#[allow(clippy::too_many_arguments)]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn handle_analyze_proof_annotations(
project_path: PathBuf,
format: ProofAnnotationOutputFormat,
high_confidence_only: bool,
include_evidence: bool,
property_type: Option<PropertyTypeFilter>,
verification_method: Option<VerificationMethodFilter>,
output: Option<PathBuf>,
_perf: bool,
clear_cache: bool,
) -> Result<()> {
use crate::cli::proof_annotation_helpers::{
collect_and_filter_annotations, format_as_full, format_as_json, format_as_markdown,
format_as_sarif, format_as_summary, setup_proof_annotator, ProofAnnotationFilter,
};
use std::time::Instant;
eprintln!("🔍 Collecting proof annotations from project...");
let start = Instant::now();
let annotator = setup_proof_annotator(clear_cache);
let filter = ProofAnnotationFilter {
high_confidence_only,
property_type,
verification_method,
};
let annotations = collect_and_filter_annotations(&annotator, &project_path, &filter).await;
let elapsed = start.elapsed();
eprintln!("✅ Found {} matching proof annotations", annotations.len());
let content = match format {
ProofAnnotationOutputFormat::Json => format_as_json(&annotations, elapsed, &annotator)?,
ProofAnnotationOutputFormat::Summary => format_as_summary(&annotations, elapsed)?,
ProofAnnotationOutputFormat::Full => {
format_as_full(&annotations, &project_path, include_evidence)?
}
ProofAnnotationOutputFormat::Markdown => {
format_as_markdown(&annotations, &project_path, include_evidence)?
}
ProofAnnotationOutputFormat::Sarif => format_as_sarif(&annotations, &project_path)?,
};
if let Some(output_path) = output {
tokio::fs::write(&output_path, &content).await?;
eprintln!("✅ Proof annotations written to: {}", output_path.display());
} else {
println!("{content}");
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn handle_analyze_incremental_coverage(
project_path: PathBuf,
base_branch: String,
target_branch: Option<String>,
format: IncrementalCoverageOutputFormat,
coverage_threshold: f64,
_changed_files_only: bool,
_detailed: bool,
output: Option<PathBuf>,
_perf: bool,
_cache_dir: Option<PathBuf>,
_force_refresh: bool,
top_files: usize,
) -> Result<()> {
print_coverage_analysis_header(
&project_path,
&base_branch,
&target_branch,
coverage_threshold,
&format,
);
use crate::cli::coverage_helpers::{get_changed_files_for_coverage, setup_coverage_analyzer};
let analyzer = setup_coverage_analyzer(_cache_dir, _force_refresh)?;
let changed_files =
get_changed_files_for_coverage(&project_path, &base_branch, target_branch.as_deref())
.await?;
let modified_files = create_file_ids_from_changes(&changed_files)?;
let changeset = crate::services::incremental_coverage_analyzer::ChangeSet {
modified_files,
added_files: Vec::new(), deleted_files: Vec::new(),
};
let coverage_update = analyzer.analyze_changes(&changeset).await?;
let report = convert_coverage_update_to_report(
coverage_update,
base_branch,
target_branch.unwrap_or("HEAD".to_string()),
coverage_threshold,
changed_files,
)?;
let content = format_coverage_report(&report, format, top_files)?;
output_coverage_result(content, output).await?;
Ok(())
}
fn print_coverage_analysis_header(
project_path: &Path,
base_branch: &str,
target_branch: &Option<String>,
coverage_threshold: f64,
format: &IncrementalCoverageOutputFormat,
) {
eprintln!("📊 Analyzing incremental coverage...");
eprintln!("📁 Project path: {}", project_path.display());
eprintln!("🌿 Base branch: {base_branch}");
eprintln!(
"🎯 Target branch: {}",
target_branch.as_deref().unwrap_or("HEAD")
);
eprintln!("📈 Coverage threshold: {:.1}%", coverage_threshold * 100.0);
eprintln!("📄 Format: {format:?}");
}
fn create_file_ids_from_changes(
changed_files: &[(PathBuf, String)],
) -> Result<Vec<crate::services::incremental_coverage_analyzer::FileId>> {
use crate::services::incremental_coverage_analyzer::FileId;
use sha2::{Digest, Sha256};
let mut modified_files = Vec::new();
for (path, status) in changed_files {
if status == "M" || status == "A" {
let mut hasher = Sha256::new();
hasher.update(path.to_string_lossy().as_bytes());
let hash_result = hasher.finalize();
let mut hash = [0u8; 32];
hash.copy_from_slice(&hash_result);
modified_files.push(FileId {
path: path.clone(),
hash,
});
}
}
Ok(modified_files)
}
fn format_coverage_report(
report: &IncrementalCoverageReport,
format: IncrementalCoverageOutputFormat,
top_files: usize,
) -> Result<String> {
use IncrementalCoverageOutputFormat::{Delta, Detailed, Json, Lcov, Markdown, Sarif, Summary};
match format {
Summary => format_incremental_coverage_summary(report, top_files),
Detailed => format_incremental_coverage_detailed(report, top_files),
Json => serde_json::to_string_pretty(report).map_err(Into::into),
Markdown => format_incremental_coverage_markdown(report, top_files),
Lcov => format_incremental_coverage_lcov(report),
Delta => format_incremental_coverage_delta(report, top_files),
Sarif => format_incremental_coverage_sarif(report),
}
}
async fn output_coverage_result(content: String, output: Option<PathBuf>) -> Result<()> {
eprintln!("✅ Incremental coverage analysis complete");
if let Some(output_path) = output {
tokio::fs::write(&output_path, &content).await?;
eprintln!("📝 Written to {}", output_path.display());
} else {
println!("{content}");
}
Ok(())
}