use std::collections::HashMap;
use serde::{Deserialize, Serialize};
pub const CURRENT_ANALYSIS_VERSION: u32 = 5;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitInfo {
pub hash: String,
pub author_name: String,
pub author_email: String,
pub message: String,
pub timestamp: i64,
pub tree_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DependencyEdge {
pub from_module: String,
pub to_module: String,
pub file_path: String,
#[serde(default)]
pub line: Option<usize>,
#[serde(default = "default_weight")]
pub weight: u32,
#[serde(default)]
pub sample_origins: Vec<EdgeOrigin>,
}
fn default_weight() -> u32 {
1
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct EdgeOrigin {
pub file_path: String,
#[serde(default)]
pub line: Option<usize>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum NodeKind {
Internal,
External,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct NodeMetadata {
pub kind: NodeKind,
#[serde(default)]
pub importer_count: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct FilteredExternalSample {
pub module_name: String,
pub importer_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct FileImportTarget {
pub module_name: String,
pub weight: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct FileDependencyState {
pub package_name: String,
#[serde(default)]
pub imports: Vec<FileImportTarget>,
#[serde(default)]
pub function_count: Option<u32>,
#[serde(default)]
pub type_count: Option<u32>,
#[serde(default)]
pub complexity: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct RepoScanState {
#[serde(default)]
pub files: HashMap<String, FileDependencyState>,
#[serde(default)]
pub module_churn: HashMap<String, u32>,
#[serde(default)]
pub module_authors: HashMap<String, HashMap<String, u32>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct GraphDelta {
#[serde(default)]
pub upserts: Vec<(String, FileDependencyState)>,
#[serde(default)]
pub deletes: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct ScanMetadata {
#[serde(default)]
pub external_min_importers: u32,
#[serde(default)]
pub included_external_count: usize,
#[serde(default)]
pub filtered_external_count: usize,
#[serde(default)]
pub filtered_external_samples: Vec<FilteredExternalSample>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct InstabilityMetric {
pub module_name: String,
pub instability: f64,
pub fan_in: usize,
pub fan_out: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphSnapshot {
pub commit_hash: String,
pub nodes: Vec<String>,
pub edges: Vec<DependencyEdge>,
pub node_count: usize,
pub edge_count: usize,
pub timestamp: i64,
#[serde(default)]
pub analysis_version: u32,
#[serde(default)]
pub config_fingerprint: String,
#[serde(default)]
pub node_metadata: HashMap<String, NodeMetadata>,
#[serde(default)]
pub scan_metadata: ScanMetadata,
#[serde(default)]
pub drift: Option<DriftScore>,
#[serde(default)]
pub blast_radius: Option<BlastRadiusReport>,
#[serde(default)]
pub instability_metrics: Vec<InstabilityMetric>,
#[serde(default)]
pub diagnostics: Vec<String>,
#[serde(default)]
pub module_churn: HashMap<String, u32>,
#[serde(default)]
pub bus_factor: Vec<BusFactorEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SnapshotMetadata {
pub commit_hash: String,
pub scan_order: i64,
pub timestamp: i64,
pub drift: Option<DriftScore>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SnapshotFrame {
pub commit_hash: String,
pub scan_order: i64,
pub timestamp: i64,
pub node_count: usize,
pub edge_count: usize,
pub analysis_version: u32,
pub config_fingerprint: String,
pub drift: Option<DriftScore>,
pub scan_metadata: ScanMetadata,
pub delta: GraphDelta,
pub has_full_artifacts: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BusFactorEntry {
pub module_name: String,
pub unique_authors: usize,
pub top_author: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HeavySnapshotArtifacts {
pub blast_radius: BlastRadiusReport,
pub instability_metrics: Vec<InstabilityMetric>,
pub diagnostics: Vec<String>,
#[serde(default)]
pub module_churn: HashMap<String, u32>,
#[serde(default)]
pub bus_factor: Vec<BusFactorEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphCheckpoint {
pub commit_hash: String,
pub scan_order: i64,
pub state: RepoScanState,
#[serde(default)]
pub full_artifacts: Option<HeavySnapshotArtifacts>,
}
impl GraphSnapshot {
pub fn requires_core_recompute(&self) -> bool {
self.analysis_version < CURRENT_ANALYSIS_VERSION
|| self.config_fingerprint.is_empty()
|| self.node_metadata.is_empty()
|| self.drift.is_none()
}
pub fn needs_runtime_insights(&self) -> bool {
self.instability_metrics.is_empty() || self.diagnostics.is_empty()
}
pub fn needs_full_analysis(&self) -> bool {
self.blast_radius.is_none()
}
pub fn requires_artifact_recompute(&self) -> bool {
self.requires_core_recompute()
|| self.needs_runtime_insights()
|| self.needs_full_analysis()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DriftScore {
pub total: u8,
pub fan_in_delta: i32,
pub fan_out_delta: i32,
pub new_cycles: usize,
pub boundary_violations: usize,
#[serde(default)]
pub layering_violations: usize,
pub cognitive_complexity: f64,
pub timestamp: i64,
#[serde(default)]
pub cycle_debt: f64,
#[serde(default)]
pub layering_debt: f64,
#[serde(default)]
pub hub_debt: f64,
#[serde(default)]
pub coupling_debt: f64,
#[serde(default)]
pub cognitive_debt: f64,
#[serde(default)]
pub instability_debt: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlastRadiusReport {
pub impacts: Vec<ModuleImpact>,
pub articulation_points: Vec<ArticulationPoint>,
pub critical_paths: Vec<CascadePath>,
pub summary: BlastRadiusSummary,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleImpact {
pub module_name: String,
pub blast_score: f64,
pub downstream_count: usize,
pub weighted_reach: f64,
pub is_articulation_point: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArticulationPoint {
pub module_name: String,
pub components_bridged: usize,
pub fan_in: usize,
pub fan_out: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CascadePath {
pub chain: Vec<String>,
pub total_weight: u32,
pub depth: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlastRadiusSummary {
pub articulation_point_count: usize,
pub max_blast_score: f64,
pub most_impactful_module: String,
pub mean_blast_score: f64,
pub longest_chain_depth: usize,
}