use std::path::PathBuf;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Branch {
pub id: Uuid,
pub name: String,
pub slug: String,
pub parent_id: Option<Uuid>,
pub status: BranchStatus,
pub db_path: PathBuf,
pub snapshot_path: PathBuf,
pub workspace_id: Uuid,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub forked_from_cursor: Option<String>,
pub description: Option<String>,
pub metadata: Value,
pub metrics: BranchMetrics,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum BranchStatus {
Active,
Dormant,
Merged {
merged_into: Uuid,
merged_at: DateTime<Utc>,
},
Discarded {
discarded_at: DateTime<Utc>,
},
Archived,
Orphan,
Purged,
}
impl BranchStatus {
pub fn is_live(&self) -> bool {
matches!(self, Self::Active | Self::Dormant)
}
pub fn can_commit(&self) -> bool {
matches!(self, Self::Active)
}
pub fn kind(&self) -> &'static str {
match self {
Self::Active => "active",
Self::Dormant => "dormant",
Self::Merged { .. } => "merged",
Self::Discarded { .. } => "discarded",
Self::Archived => "archived",
Self::Orphan => "orphan",
Self::Purged => "purged",
}
}
pub fn to_storage(&self) -> String {
match self {
Self::Active => "active".to_string(),
Self::Dormant => "dormant".to_string(),
Self::Archived => "archived".to_string(),
Self::Orphan => "orphan".to_string(),
Self::Purged => "purged".to_string(),
Self::Merged {
merged_into,
merged_at,
} => format!("merged:{merged_into}:{merged_at}"),
Self::Discarded { discarded_at } => format!("discarded:{discarded_at}"),
}
}
pub fn from_storage(value: &str) -> Option<Self> {
match value {
"active" => Some(Self::Active),
"dormant" => Some(Self::Dormant),
"archived" => Some(Self::Archived),
"orphan" => Some(Self::Orphan),
"purged" => Some(Self::Purged),
_ if value.starts_with("merged:") => {
let mut parts = value.splitn(3, ':');
let _ = parts.next();
let merged_into = parts.next().and_then(|part| Uuid::parse_str(part).ok())?;
let merged_at = parts
.next()
.and_then(|part| part.parse::<DateTime<Utc>>().ok())?;
Some(Self::Merged {
merged_into,
merged_at,
})
}
_ if value.starts_with("discarded:") => {
let discarded_at = value
.split_once(':')
.and_then(|(_, timestamp)| timestamp.parse::<DateTime<Utc>>().ok())?;
Some(Self::Discarded { discarded_at })
}
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct BranchMetrics {
pub op_count: u64,
pub memory_record_count: i64,
pub session_count: i64,
pub tool_output_count: i64,
pub bytes_on_disk: u64,
pub divergence_score: f64,
pub created_entity_count: u64,
pub updated_entity_count: u64,
pub deleted_entity_count: u64,
pub last_activity_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DiffResult {
pub branch_a_id: Uuid,
pub branch_b_id: Uuid,
pub compared_at: DateTime<Utc>,
pub entity_diffs: Vec<EntityDiff>,
pub stats: DiffStats,
pub divergence_score: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EntityDiff {
pub entity_id: String,
pub entity_type: EntityType,
pub diff_kind: DiffKind,
pub field_diffs: Vec<FieldDiff>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum DiffKind {
Added,
Removed,
Modified,
Unchanged,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FieldDiff {
pub field: String,
pub before: Value,
pub after: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct DiffStats {
pub added: u32,
pub removed: u32,
pub modified: u32,
pub unchanged: u32,
pub total_entities: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MergeConflict {
pub entity_id: String,
pub entity_type: EntityType,
pub base_value: Value,
pub ours_value: Value,
pub theirs_value: Value,
pub conflicting_fields: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MergeResult {
pub source_branch_id: Uuid,
pub target_branch_id: Uuid,
pub base_branch_id: Uuid,
pub applied: u32,
pub skipped: u32,
pub conflicts: Vec<MergeConflict>,
pub duration_ms: u64,
pub success: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum EntityType {
MemoryRecord,
Session,
ToolOutput,
}
impl EntityType {
pub fn table_name(&self) -> &'static str {
match self {
Self::MemoryRecord => "memory_records",
Self::Session => "sessions",
Self::ToolOutput => "tool_outputs",
}
}
pub fn parse(value: &str) -> Option<Self> {
match value {
"memory_record" | "memory_records" => Some(Self::MemoryRecord),
"session" | "sessions" => Some(Self::Session),
"tool_output" | "tool_outputs" => Some(Self::ToolOutput),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::MemoryRecord => "memory_record",
Self::Session => "session",
Self::ToolOutput => "tool_output",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CommitLogEntry {
pub id: Uuid,
pub branch_id: Uuid,
pub entity_type: Option<EntityType>,
pub entity_ids: Vec<String>,
pub op_kind: String,
pub committed_at: DateTime<Utc>,
pub message: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct CommitResult {
pub committed_entity_count: u32,
pub fields_updated: u32,
pub duration_ms: u64,
pub target_branch_id: Uuid,
pub committed_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SimulationOutcome {
pub agent_output: Value,
pub ops_executed: u32,
pub duration_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct WorkspaceReport {
pub branch_count: u32,
pub total_disk_bytes: u64,
pub avg_divergence_score: f64,
pub most_active_branch: Option<Uuid>,
pub stale_branch_ids: Vec<Uuid>,
pub report_at: DateTime<Utc>,
}