use crate::services::lightweight_provability_analyzer::{FunctionId, ProofSummary};
use anyhow::Result;
use std::collections::HashMap;
use std::fmt::Write;
use std::path::Path;
pub fn parse_function_spec(spec: &str, project_path: &Path) -> Result<FunctionId> {
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, })
} else {
Ok(FunctionId {
file_path: String::new(),
function_name: spec.to_string(),
line_number: 0,
})
}
}
pub async fn discover_project_functions(project_path: &Path) -> Result<Vec<FunctionId>> {
eprintln!("📂 Discovering functions in project...");
let mut function_ids = Vec::new();
let mut file_count = 0;
use crate::services::file_discovery::{FileDiscoveryConfig, ProjectFileDiscovery};
let discovery = ProjectFileDiscovery::new(project_path.to_path_buf())
.with_config(FileDiscoveryConfig::default());
let files = discovery.discover_files()?;
for path in files {
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
if matches!(ext, "rs" | "ts" | "js" | "py" | "c" | "cpp" | "h" | "hpp") {
file_count += 1;
let relative_path = path
.strip_prefix(project_path)
.unwrap_or(&path)
.to_string_lossy()
.to_string();
function_ids.push(FunctionId {
file_path: relative_path.clone(),
function_name: "main".to_string(),
line_number: 1,
});
function_ids.push(FunctionId {
file_path: relative_path,
function_name: "test".to_string(),
line_number: 10,
});
}
}
eprintln!("📊 Found {file_count} source files");
Ok(function_ids)
}
#[allow(dead_code)]
fn extract_function_name(line: &str) -> Option<String> {
let line = line.trim();
let start = line.find("fn ")? + 3;
let end = line[start..].find(['(', '<'])?;
Some(line[start..start + end].trim().to_string())
}
#[must_use]
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()
}
pub fn format_provability_json(
function_ids: &[FunctionId],
summaries: &[ProofSummary],
include_evidence: bool,
) -> Result<String> {
let results: Vec<_> = function_ids
.iter()
.zip(summaries.iter())
.map(|(func_id, summary)| {
let mut result = serde_json::json!({
"function": {
"name": func_id.function_name,
"file": func_id.file_path,
"line": func_id.line_number,
},
"provability_score": summary.provability_score,
"analysis_time_us": summary.analysis_time_us,
"verified_properties": summary.verified_properties.len(),
});
if include_evidence {
result["properties"] = serde_json::json!(summary
.verified_properties
.iter()
.map(|prop| {
serde_json::json!({
"type": format!("{:?}", prop.property_type),
"confidence": prop.confidence,
"evidence": prop.evidence,
})
})
.collect::<Vec<_>>());
}
result
})
.collect();
let output = serde_json::json!({
"provability_analysis": {
"total_functions": function_ids.len(),
"results": results,
}
});
serde_json::to_string_pretty(&output).map_err(Into::into)
}
pub fn format_provability_summary(
function_ids: &[FunctionId],
summaries: &[ProofSummary],
top_files: usize,
) -> Result<String> {
let mut output = String::new();
write_summary_header(&mut output, function_ids.len())?;
write_score_distribution(&mut output, summaries)?;
write_average_score(&mut output, summaries)?;
write_top_files_section(&mut output, function_ids, summaries, top_files)?;
Ok(output)
}
fn write_summary_header(output: &mut String, total_functions: usize) -> Result<()> {
writeln!(output, "# Provability Analysis Summary\n")?;
writeln!(output, "Total functions analyzed: {total_functions}")?;
Ok(())
}
fn write_score_distribution(output: &mut String, summaries: &[ProofSummary]) -> Result<()> {
let (high_count, medium_count, low_count) = categorize_scores(summaries);
writeln!(output, "\n## Score Distribution:")?;
writeln!(output, "- High (≥80%): {high_count} functions")?;
writeln!(output, "- Medium (50-79%): {medium_count} functions")?;
writeln!(output, "- Low (<50%): {low_count} functions")?;
Ok(())
}
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 write_average_score(output: &mut String, summaries: &[ProofSummary]) -> Result<()> {
let avg_score = calculate_average_score(summaries);
writeln!(
output,
"\nAverage provability score: {:.1}%",
avg_score * 100.0
)?;
Ok(())
}
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
}
}
fn write_top_files_section(
output: &mut String,
function_ids: &[FunctionId],
summaries: &[ProofSummary],
top_files: usize,
) -> Result<()> {
if function_ids.is_empty() {
return Ok(());
}
writeln!(output, "\n## Top Files by Provability\n")?;
let file_avg_scores = calculate_file_averages(function_ids, summaries);
write_top_files_list(output, &file_avg_scores, top_files)?;
Ok(())
}
fn calculate_file_averages<'a>(
function_ids: &'a [FunctionId],
summaries: &'a [ProofSummary],
) -> Vec<(&'a str, f64, usize)> {
let mut file_scores: HashMap<&str, Vec<f64>> = HashMap::new();
for (func_id, summary) in function_ids.iter().zip(summaries.iter()) {
file_scores
.entry(&func_id.file_path)
.or_default()
.push(summary.provability_score);
}
let mut file_avg_scores: Vec<_> = file_scores
.iter()
.map(|(file_path, scores)| {
let avg_score = scores.iter().sum::<f64>() / scores.len() as f64;
(*file_path, avg_score, scores.len())
})
.collect();
file_avg_scores.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
file_avg_scores
}
fn write_top_files_list(
output: &mut String,
file_avg_scores: &[(&str, f64, usize)],
top_files: usize,
) -> Result<()> {
let files_to_show = if top_files == 0 { 10 } else { top_files };
for (i, (file_path, avg_score, function_count)) in
file_avg_scores.iter().take(files_to_show).enumerate()
{
let filename = extract_filename(file_path);
writeln!(
output,
"{}. `{}` - {:.1}% avg score ({} functions)",
i + 1,
filename,
avg_score * 100.0,
function_count
)?;
}
Ok(())
}
fn extract_filename(file_path: &str) -> &str {
std::path::Path::new(file_path)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(file_path)
}
pub fn format_provability_detailed(
function_ids: &[FunctionId],
summaries: &[ProofSummary],
include_evidence: bool,
) -> Result<String> {
let mut output = String::new();
writeln!(&mut output, "# Detailed Provability Analysis\n")?;
let by_file = group_functions_by_file(function_ids, summaries);
write_detailed_analysis_by_file(&mut output, by_file, include_evidence)?;
Ok(output)
}
fn group_functions_by_file<'a>(
function_ids: &'a [FunctionId],
summaries: &'a [ProofSummary],
) -> HashMap<&'a str, Vec<(&'a FunctionId, &'a ProofSummary)>> {
let mut by_file = HashMap::new();
for (func_id, summary) in function_ids.iter().zip(summaries.iter()) {
by_file
.entry(func_id.file_path.as_str())
.or_insert_with(Vec::new)
.push((func_id, summary));
}
by_file
}
fn write_detailed_analysis_by_file(
output: &mut String,
by_file: HashMap<&str, Vec<(&FunctionId, &ProofSummary)>>,
include_evidence: bool,
) -> Result<()> {
for (file_path, functions) in by_file {
write_file_section(output, file_path, &functions, include_evidence)?;
}
Ok(())
}
fn write_file_section(
output: &mut String,
file_path: &str,
functions: &[(&FunctionId, &ProofSummary)],
include_evidence: bool,
) -> Result<()> {
writeln!(output, "## {file_path}\n")?;
for (func_id, summary) in functions {
write_function_details(output, func_id, summary)?;
if include_evidence && !summary.verified_properties.is_empty() {
write_verified_properties(output, &summary.verified_properties)?;
}
writeln!(output)?;
}
Ok(())
}
fn write_function_details(
output: &mut String,
func_id: &FunctionId,
summary: &ProofSummary,
) -> Result<()> {
writeln!(output, "### Function: `{}`", func_id.function_name)?;
writeln!(output, "- **Line**: {}", func_id.line_number)?;
writeln!(
output,
"- **Provability Score**: {:.1}%",
summary.provability_score * 100.0
)?;
writeln!(
output,
"- **Analysis Time**: {}μs",
summary.analysis_time_us
)?;
writeln!(
output,
"- **Verified Properties**: {}",
summary.verified_properties.len()
)?;
Ok(())
}
fn write_verified_properties(
output: &mut String,
properties: &[crate::services::lightweight_provability_analyzer::VerifiedProperty],
) -> Result<()> {
writeln!(output, "\n#### Verified Properties:")?;
for prop in properties {
writeln!(
output,
"- **{:?}** (confidence: {:.0}%)",
prop.property_type,
prop.confidence * 100.0
)?;
writeln!(output, " - Evidence: {}", prop.evidence)?;
}
Ok(())
}
pub fn format_provability_sarif(
function_ids: &[FunctionId],
summaries: &[ProofSummary],
) -> Result<String> {
let mut results = Vec::new();
for (func_id, summary) in function_ids.iter().zip(summaries.iter()) {
let rule_id = if summary.provability_score < 0.5 {
"low-provability"
} else if summary.provability_score < 0.8 {
"medium-provability"
} else {
"high-provability"
};
let level = if summary.provability_score < 0.5 {
"warning"
} else if summary.provability_score < 0.8 {
"note"
} else {
"none"
};
results.push(serde_json::json!({
"ruleId": rule_id,
"level": level,
"message": {
"text": format!(
"Function '{}' has {:.0}% provability score",
func_id.function_name,
summary.provability_score * 100.0
)
},
"locations": [{
"physicalLocation": {
"artifactLocation": {
"uri": func_id.file_path
},
"region": {
"startLine": func_id.line_number
}
}
}]
}));
}
let sarif = serde_json::json!({
"version": "2.1.0",
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"runs": [{
"tool": {
"driver": {
"name": "paiml-provability-analyzer",
"version": env!("CARGO_PKG_VERSION"),
"informationUri": "https://github.com/paiml/paiml-mcp-agent-toolkit",
"rules": generate_provability_rules(),
}
},
"results": results
}]
});
serde_json::to_string_pretty(&sarif).map_err(Into::into)
}
fn generate_provability_rules() -> Vec<serde_json::Value> {
vec![
serde_json::json!({
"id": "low-provability",
"name": "Low Provability Score",
"shortDescription": {
"text": "Function has low formal verification confidence"
},
"fullDescription": {
"text": "Functions with low provability scores may contain complex control flow or lack sufficient type information for verification"
},
"defaultConfiguration": {
"level": "warning"
}
}),
serde_json::json!({
"id": "medium-provability",
"name": "Medium Provability Score",
"shortDescription": {
"text": "Function has medium formal verification confidence"
},
"fullDescription": {
"text": "Functions with medium provability scores can be partially verified but may have some unverifiable properties"
},
"defaultConfiguration": {
"level": "note"
}
}),
serde_json::json!({
"id": "high-provability",
"name": "High Provability Score",
"shortDescription": {
"text": "Function has high formal verification confidence"
},
"fullDescription": {
"text": "Functions with high provability scores can be formally verified with high confidence"
},
"defaultConfiguration": {
"level": "none"
}
}),
]
}
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}