impl Default for SelfDiagnostic {
fn default() -> Self {
Self::new()
}
}
impl SelfDiagnostic {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new() -> Self {
Self {
tests: vec![
Box::new(RustAstTest),
Box::new(TypeScriptAstTest),
Box::new(PythonAstTest),
Box::new(ComplexityAnalysisTest),
Box::new(DeepContextTest),
Box::new(CacheSubsystemTest),
Box::new(GitIntegrationTest),
Box::new(MermaidGeneratorTest),
],
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn run_diagnostic(&self, args: &DiagnoseArgs) -> DiagnosticReport {
let start = Instant::now();
let mut features = BTreeMap::new();
for test in &self.tests {
let test_name = test.name();
if !args.only.is_empty() && !args.only.contains(&test_name.to_string()) {
continue;
}
if args.skip.contains(&test_name.to_string()) {
features.insert(
test_name.to_string(),
FeatureResult {
status: FeatureStatus::Skipped("User requested skip".to_string()),
duration_us: 0,
error: None,
metrics: None,
},
);
continue;
}
let test_start = Instant::now();
let result =
match timeout(Duration::from_secs(args.timeout.min(10)), test.execute()).await {
Ok(Ok(metrics)) => FeatureResult {
status: FeatureStatus::Ok,
duration_us: test_start.elapsed().as_micros() as u64,
error: None,
metrics: Some(metrics),
},
Ok(Err(e)) => FeatureResult {
status: FeatureStatus::Failed,
duration_us: test_start.elapsed().as_micros() as u64,
error: Some(format!("{e:?}")),
metrics: None,
},
Err(_) => FeatureResult {
status: FeatureStatus::Failed,
duration_us: 10_000_000, error: Some("Test timeout after 10s".into()),
metrics: None,
},
};
features.insert(test_name.to_string(), result);
}
let summary = self.compute_summary(&features);
let error_context = self.extract_error_context(&features);
DiagnosticReport {
version: env!("CARGO_PKG_VERSION").to_string(),
build_info: BuildInfo::current(),
timestamp: Utc::now(),
duration_ms: start.elapsed().as_millis() as u64,
features,
summary,
error_context,
}
}
fn validate_filters(&self, args: &DiagnoseArgs) -> Result<()> {
let known: Vec<&str> = self.tests.iter().map(|t| t.name()).collect();
for (flag, requested) in [("--only", &args.only), ("--skip", &args.skip)] {
for name in requested {
if !known.contains(&name.as_str()) {
anyhow::bail!(
"unknown {flag} feature test '{name}'. Available: {}",
known.join(", ")
);
}
}
}
Ok(())
}
fn compute_summary(&self, features: &BTreeMap<String, FeatureResult>) -> DiagnosticSummary {
let total = features.len();
let mut passed = 0;
let mut failed = 0;
let mut degraded = 0;
let mut skipped = 0;
for result in features.values() {
match &result.status {
FeatureStatus::Ok => passed += 1,
FeatureStatus::Failed => failed += 1,
FeatureStatus::Degraded(_) => degraded += 1,
FeatureStatus::Skipped(_) => skipped += 1,
}
}
DiagnosticSummary {
total,
passed,
failed,
degraded,
skipped,
all_passed: failed == 0 && degraded == 0,
success_rate: {
let executed = total - skipped;
if executed > 0 {
#[allow(clippy::cast_precision_loss)]
Some((passed as f64 / executed as f64) * 100.0)
} else {
None
}
},
}
}
fn extract_error_context(
&self,
features: &BTreeMap<String, FeatureResult>,
) -> Option<CompactErrorContext> {
let failed: Vec<_> = features
.iter()
.filter(|(_, r)| matches!(r.status, FeatureStatus::Failed))
.map(|(name, _)| name.clone())
.collect();
if failed.is_empty() {
return None;
}
let mut error_patterns = BTreeMap::new();
for (feature, result) in features {
if let Some(error) = &result.error {
let pattern = self.classify_error(error);
error_patterns
.entry(pattern)
.or_insert_with(Vec::new)
.push(feature.clone());
}
}
Some(CompactErrorContext {
failed_features: failed,
error_patterns: error_patterns.clone(),
suggested_fixes: self.generate_fixes(&error_patterns),
environment: EnvironmentSnapshot::capture(),
})
}
fn classify_error(&self, error: &str) -> String {
if error.contains("Permission denied") {
"permission_denied".into()
} else if error.contains("not found") {
"file_not_found".into()
} else if error.contains("timeout") {
"timeout".into()
} else if error.contains("git") {
"git_error".into()
} else {
"unknown".into()
}
}
fn generate_fixes(&self, error_patterns: &BTreeMap<String, Vec<String>>) -> Vec<SuggestedFix> {
let mut fixes = Vec::new();
for (pattern, features) in error_patterns {
let fix = match pattern.as_str() {
"permission_denied" => SuggestedFix {
feature: features.join(", "),
error_pattern: pattern.clone(),
fix_command: Some("chmod +r <file>".into()),
documentation_link: None,
},
"git_error" => SuggestedFix {
feature: features.join(", "),
error_pattern: pattern.clone(),
fix_command: Some("git init".into()),
documentation_link: Some(
"https://github.com/paiml/paiml-mcp-agent-toolkit#git-integration".into(),
),
},
_ => SuggestedFix {
feature: features.join(", "),
error_pattern: pattern.clone(),
fix_command: None,
documentation_link: None,
},
};
fixes.push(fix);
}
fixes
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn handle_diagnose(args: DiagnoseArgs) -> Result<()> {
let diagnostic = SelfDiagnostic::new();
diagnostic.validate_filters(&args)?;
let report = diagnostic.run_diagnostic(&args).await;
match args.format {
DiagnosticFormat::Pretty => print_pretty_report(&report),
DiagnosticFormat::Json => {
println!("{}", serde_json::to_string_pretty(&report)?);
}
DiagnosticFormat::Compact => {
let compact = json!({
"v": report.version,
"ok": report.summary.all_passed,
"failed": report.error_context.as_ref().map(|c| &c.failed_features),
"fixes": report.error_context.as_ref().map(|c| &c.suggested_fixes),
});
println!("{}", serde_json::to_string(&compact)?);
}
}
Ok(())
}
fn print_pretty_report(report: &DiagnosticReport) {
print!("{}", format_pretty_report(report));
}
pub(crate) fn format_pretty_report(report: &DiagnosticReport) -> String {
use crate::cli::colors as c;
use std::fmt::Write as _;
let mut out = String::new();
let _ = writeln!(out, "{}", c::header("PMAT Self-Diagnostic Report"));
let _ = writeln!(
out,
" {}: {} {}: {}ms",
c::label("Version"),
c::number(&report.version),
c::label("Duration"),
c::number(&report.duration_ms.to_string()),
);
let _ = writeln!(out);
for (feature, result) in &report.features {
let timing = c::dim(&format!("({}\u{3bc}s)", result.duration_us));
let line = match result.status {
FeatureStatus::Ok => c::pass(&format!("{} {timing}", c::path(feature))),
FeatureStatus::Degraded(_) => c::warn(&format!("{} {timing}", c::path(feature))),
FeatureStatus::Failed => c::fail(&format!("{} {timing}", c::path(feature))),
FeatureStatus::Skipped(_) => c::skip(&format!("{feature} {timing}")),
};
let _ = writeln!(out, "{line}");
if let Some(error) = &result.error {
let _ = writeln!(
out,
" {}",
c::colored(c::RED, &format!("\u{2514}\u{2500} {error}"))
);
}
}
let _ = writeln!(out);
let _ = writeln!(out, "{}", c::subheader("Summary:"));
let _ = writeln!(
out,
" {}: {}",
c::label("Total"),
c::number(&report.summary.total.to_string())
);
let _ = writeln!(
out,
" {}: {}",
c::label("Passed"),
c::colored(c::GREEN, &report.summary.passed.to_string())
);
let _ = writeln!(
out,
" {}: {}",
c::label("Failed"),
c::colored(
if report.summary.failed > 0 {
c::RED
} else {
c::GREEN
},
&report.summary.failed.to_string()
)
);
let _ = writeln!(
out,
" {}: {}",
c::label("Success Rate"),
match report.summary.success_rate {
Some(rate) => c::pct(rate, 100.0, 80.0),
None => c::dim("not measured (no check executed)"),
}
);
if let Some(ctx) = &report.error_context {
let _ = writeln!(out);
let _ = writeln!(out, "{}", c::subheader("Suggested Fixes:"));
for fix in &ctx.suggested_fixes {
let _ = writeln!(
out,
" {} {}: {}",
c::warn(""),
c::label(&fix.feature),
c::dim(
fix.fix_command
.as_ref()
.unwrap_or(&"See documentation".into())
)
);
}
}
out
}