use crate::binding::Workspace;
use crate::budgets::StepBill;
use crate::git_tree::GitTree;
use crate::nav::convs;
use crate::opslog::OpRow;
use crate::projects::balls::Ball;
use crate::projects::join::JoinRow;
use std::collections::{BTreeMap, HashMap};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Growth {
pub workspace: PathBuf,
pub conversation: String,
pub added: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Snapshot {
pub workspaces: Vec<Workspace>,
pub projects: Vec<PathBuf>,
pub trees: HashMap<PathBuf, GitTree>,
pub bills: HashMap<PathBuf, Vec<StepBill>>,
pub windows: std::collections::BTreeMap<String, u64>,
pub balls_by_project: HashMap<PathBuf, Vec<Ball>>,
pub closed_by_project: HashMap<PathBuf, Vec<Ball>>,
pub join_rows: Vec<JoinRow>,
pub ops: Vec<OpRow>,
pub growth: Vec<Growth>,
pub ui_bytes: Option<Vec<u8>>,
pub derived_at_unix: i64,
pub cadence: super::Cadence,
pub fleet: BTreeMap<String, crate::fleet::Policy>,
}
mod names;
pub(crate) use names::addressable;
mod scope;
impl Snapshot {
pub(crate) fn empty(now_unix: i64) -> Self {
Self {
workspaces: Vec::new(),
projects: Vec::new(),
trees: HashMap::new(),
bills: HashMap::new(),
windows: BTreeMap::new(),
balls_by_project: HashMap::new(),
closed_by_project: HashMap::new(),
join_rows: Vec::new(),
ops: Vec::new(),
growth: Vec::new(),
ui_bytes: None,
derived_at_unix: now_unix,
cadence: super::Cadence::default(),
fleet: BTreeMap::new(),
}
}
}
pub fn growth_label(growth: &[Growth]) -> Option<String> {
let worst = growth.first()?;
let rest = growth.len() - 1;
let more = match rest {
0 => String::new(),
n => format!(" (and {n} more)"),
};
Some(format!(
"{} +{} branches{more}",
worst.conversation, worst.added
))
}
pub(crate) fn branch_counts(tree: &GitTree) -> HashMap<String, usize> {
let mut out: HashMap<String, usize> = HashMap::new();
for agent in &tree.agents {
if let Some(root) = convs::root_of(&tree.agents, &agent.agent_id) {
*out.entry(root).or_default() += 1;
}
}
out
}
pub(crate) fn growth_between(ws: &Path, old: Option<&GitTree>, new: &GitTree) -> Vec<Growth> {
let Some(old) = old else {
return Vec::new();
};
let before = branch_counts(old);
let mut out: Vec<Growth> = Vec::new();
for (root, after) in branch_counts(new) {
let Some(prior) = before.get(&root) else {
continue;
};
if let Some(added) = after.checked_sub(*prior).filter(|n| *n > 0) {
out.push(Growth {
workspace: ws.to_path_buf(),
conversation: convs::display_name_of(&new.agents, &root),
added,
});
}
}
out.sort_by(|a, b| {
b.added
.cmp(&a.added)
.then(a.conversation.cmp(&b.conversation))
});
out
}
#[cfg(test)]
mod tests;