#![allow(unused)]
#![cfg_attr(coverage_nightly, coverage(off))]
use super::types::*;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::Path;
const SOVEREIGN_CRATES: &[&str] = &[
"aprender",
"aprender-compute",
"aprender-contracts",
"aprender-contracts-macros",
"aprender-profile",
"aprender-rag",
"aprender-simulate",
"trueno",
"trueno-graph",
"trueno-db",
"trueno-rag",
"trueno-viz",
"trueno-zram-core",
"pmcp",
"presentar-core",
"renacer",
"simular",
"certeza",
"bashrs",
"probar",
"ruchy",
"rmedia",
"whisper-apr",
];
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DependencyCountReport {
pub direct_count: usize,
pub transitive_count: usize,
pub prod_transitive_count: Option<usize>,
pub score: u8, pub duplicate_crates: Vec<DuplicateCrate>,
pub feature_gated_count: usize,
pub feature_gated_pct: f64,
pub sovereign_crates: Vec<String>,
pub sovereign_bonus: u8, pub trend: Option<DependencyTrend>,
pub violations: Vec<CbPatternViolation>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DuplicateCrate {
pub name: String,
pub versions: Vec<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DependencyTrend {
pub direct_delta: i32,
pub transitive_delta: i32,
pub previous_timestamp: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub(super) struct DependencyCache {
cargo_lock_mtime: u64,
transitive_count: usize,
#[serde(default)]
prod_transitive_count: Option<usize>,
duplicate_crates: Vec<DuplicateCrate>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentContextReport {
pub index_exists: bool,
pub index_age_hours: Option<f64>,
pub index_stale: bool,
pub function_count: usize,
pub claude_md_configured: bool,
pub missing_required_patterns: Vec<String>,
pub forbidden_patterns_found: Vec<ForbiddenPatternMatch>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ForbiddenPatternMatch {
pub pattern: String,
pub line: usize,
pub context: String,
}
include!("dependency_checks_cache.rs");
include!("dependency_checks_analysis.rs");
include!("dependency_checks_agent_context.rs");
#[cfg(test)]
mod analysis_tests {
use super::*;
#[test]
fn test_is_dependency_section_dependencies_exact() {
assert_eq!(
is_dependency_section("[dependencies]"),
(true, false, false)
);
}
#[test]
fn test_is_dependency_section_dependencies_table_prefix() {
assert_eq!(
is_dependency_section("[dependencies.serde]"),
(true, false, false)
);
}
#[test]
fn test_is_dependency_section_target_prefix_counts_as_dependencies() {
assert_eq!(
is_dependency_section("[target.'cfg(unix)'.dependencies]"),
(true, false, false)
);
}
#[test]
fn test_is_dependency_section_workspace_dependencies() {
assert_eq!(
is_dependency_section("[workspace.dependencies]"),
(true, false, false)
);
}
#[test]
fn test_is_dependency_section_dev_dependencies() {
assert_eq!(
is_dependency_section("[dev-dependencies]"),
(false, true, false)
);
}
#[test]
fn test_is_dependency_section_dev_table_prefix() {
assert_eq!(
is_dependency_section("[dev-dependencies.serde]"),
(false, true, false)
);
}
#[test]
fn test_is_dependency_section_build_dependencies() {
assert_eq!(
is_dependency_section("[build-dependencies]"),
(false, false, true)
);
}
#[test]
fn test_is_dependency_section_build_table_prefix() {
assert_eq!(
is_dependency_section("[build-dependencies.bindgen]"),
(false, false, true)
);
}
#[test]
fn test_is_dependency_section_unrelated_returns_all_false() {
assert_eq!(is_dependency_section("[package]"), (false, false, false));
assert_eq!(is_dependency_section("[features]"), (false, false, false));
assert_eq!(is_dependency_section("[lib]"), (false, false, false));
}
#[test]
fn test_is_scoreable_dependency_in_deps_with_eq() {
assert!(is_scoreable_dependency(true, false, false, "serde = \"1\""));
}
#[test]
fn test_is_scoreable_dependency_in_dev_rejected() {
assert!(!is_scoreable_dependency(true, true, false, "serde = \"1\""));
}
#[test]
fn test_is_scoreable_dependency_in_build_rejected() {
assert!(!is_scoreable_dependency(true, false, true, "serde = \"1\""));
}
#[test]
fn test_is_scoreable_dependency_no_eq_rejected() {
assert!(!is_scoreable_dependency(true, false, false, "serde"));
}
#[test]
fn test_is_scoreable_dependency_comment_rejected() {
assert!(!is_scoreable_dependency(
true,
false,
false,
"# serde = \"1\""
));
}
#[test]
fn test_is_scoreable_dependency_outside_deps_rejected() {
assert!(!is_scoreable_dependency(
false,
false,
false,
"serde = \"1\""
));
}
#[test]
fn test_process_dependency_line_simple_is_direct_not_gated() {
let mut sov = Vec::new();
let (direct, gated) = process_dependency_line("serde = \"1\"", &mut sov);
assert!(direct);
assert!(!gated);
assert!(sov.is_empty());
}
#[test]
fn test_process_dependency_line_optional_is_not_direct() {
let mut sov = Vec::new();
let (direct, _) = process_dependency_line(
"lazy_static = { version = \"1\", optional = true }",
&mut sov,
);
assert!(!direct, "optional=true should mark not-direct");
}
#[test]
fn test_process_dependency_line_default_features_false_is_gated() {
let mut sov = Vec::new();
let (_, gated) = process_dependency_line(
"serde = { version = \"1\", default-features = false }",
&mut sov,
);
assert!(gated);
}
#[test]
fn test_process_dependency_line_pin_optional_keyword_substring_marks_not_direct() {
let mut sov = Vec::new();
let (direct, _) = process_dependency_line("x = \"optional yet true gibberish\"", &mut sov);
assert!(!direct, "substring match treats this as optional=true");
}
#[test]
fn test_process_dependency_line_sovereign_crate_appended() {
let mut sov = Vec::new();
let _ = process_dependency_line("aprender = \"0.1\"", &mut sov);
assert!(sov.contains(&"aprender".to_string()));
}
#[test]
fn test_process_dependency_line_sovereign_crate_not_at_start_skipped() {
let mut sov = Vec::new();
let _ = process_dependency_line("apr-aprender = \"0.1\"", &mut sov);
assert!(sov.is_empty());
}
#[test]
fn test_build_threshold_violation_within_thresholds_returns_none() {
let v = build_threshold_violation(
"Cargo.toml",
10, 50, 50, 250, Severity::Error,
);
assert!(v.is_none());
}
#[test]
fn test_build_threshold_violation_direct_exceeded_error() {
let v = build_threshold_violation("Cargo.toml", 60, 50, 50, 250, Severity::Error).unwrap();
assert_eq!(v.pattern_id, "CB-081-A");
assert!(v.description.contains("60 direct deps exceed max 50"));
assert!(v.description.contains("50 transitive OK"));
assert!(matches!(v.severity, Severity::Error));
}
#[test]
fn test_build_threshold_violation_transitive_exceeded_warning_uses_threshold_phrasing() {
let v =
build_threshold_violation("Cargo.toml", 10, 300, 40, 200, Severity::Warning).unwrap();
assert!(v
.description
.contains("300 prod transitive deps (threshold 200)"));
assert!(v.description.contains("10 direct OK"));
assert!(matches!(v.severity, Severity::Warning));
}
#[test]
fn test_build_threshold_violation_both_exceeded_no_ok_parts_no_parens() {
let v = build_threshold_violation("Cargo.toml", 60, 300, 50, 250, Severity::Error).unwrap();
assert!(!v.description.contains("("));
assert!(v.description.contains("60 direct deps exceed max 50"));
assert!(v
.description
.contains("300 prod transitive deps exceed max 250"));
}
#[test]
fn test_build_threshold_violation_carries_file_and_zero_line() {
let v =
build_threshold_violation("/abs/Cargo.toml", 60, 50, 50, 250, Severity::Error).unwrap();
assert_eq!(v.file, "/abs/Cargo.toml");
assert_eq!(v.line, 0);
}
#[test]
fn test_check_duplicate_crates_violation_empty_appends_nothing() {
let mut violations = Vec::new();
check_duplicate_crates_violation("Cargo.lock", &[], &mut violations);
assert!(violations.is_empty());
}
#[test]
fn test_check_duplicate_crates_violation_lists_names() {
let dups = vec![
DuplicateCrate {
name: "a".into(),
versions: vec!["1".into(), "2".into()],
},
DuplicateCrate {
name: "b".into(),
versions: vec!["1".into(), "2".into()],
},
];
let mut violations = Vec::new();
check_duplicate_crates_violation("Cargo.lock", &dups, &mut violations);
assert_eq!(violations.len(), 1);
assert_eq!(violations[0].pattern_id, "CB-081-B");
assert!(violations[0].description.contains("2 duplicate crates"));
assert!(violations[0].description.contains("a, b"));
assert!(matches!(violations[0].severity, Severity::Warning));
}
#[test]
fn test_check_trend_regression_no_trend_no_violation() {
let mut violations = Vec::new();
check_trend_regression_violation("Cargo.toml", &None, 100, &mut violations);
assert!(violations.is_empty());
}
#[test]
fn test_check_trend_regression_no_increase_no_violation() {
let trend = Some(DependencyTrend {
direct_delta: 0,
transitive_delta: 0,
previous_timestamp: "2026-04-01".into(),
});
let mut violations = Vec::new();
check_trend_regression_violation("Cargo.toml", &trend, 100, &mut violations);
assert!(violations.is_empty());
}
#[test]
fn test_check_trend_regression_small_increase_no_violation() {
let trend = Some(DependencyTrend {
direct_delta: 0,
transitive_delta: 5,
previous_timestamp: "2026-04-01".into(),
});
let mut violations = Vec::new();
check_trend_regression_violation("Cargo.toml", &trend, 100, &mut violations);
assert!(violations.is_empty());
}
#[test]
fn test_check_trend_regression_large_increase_emits_warning() {
let trend = Some(DependencyTrend {
direct_delta: 0,
transitive_delta: 20,
previous_timestamp: "2026-04-01T00:00:00Z".into(),
});
let mut violations = Vec::new();
check_trend_regression_violation("Cargo.toml", &trend, 100, &mut violations);
assert_eq!(violations.len(), 1);
assert_eq!(violations[0].pattern_id, "CB-081-E");
assert!(violations[0].description.contains("Dependency creep"));
assert!(violations[0].description.contains("+20 transitive deps"));
assert!(matches!(violations[0].severity, Severity::Warning));
}
#[test]
fn test_check_trend_regression_pin_negative_delta_branch_unreachable() {
let trend = Some(DependencyTrend {
direct_delta: 0,
transitive_delta: -50,
previous_timestamp: "2026-04-01".into(),
});
let mut violations = Vec::new();
check_trend_regression_violation("Cargo.toml", &trend, 100, &mut violations);
assert!(violations.is_empty());
}
}