repopilot 0.17.0

Local-first CLI for reviewing Git changes, security boundaries, and blast radius before merge.
Documentation
use super::topology;
use crate::graph::v2::{GraphNodeId, GraphSnapshot};
use std::collections::{BTreeSet, VecDeque};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GraphNeighborhood {
    pub center: GraphNodeId,
    pub node_ids: Vec<GraphNodeId>,
    pub edge_count: usize,
}

pub fn neighborhood(
    snapshot: &GraphSnapshot,
    center: &GraphNodeId,
    depth: usize,
) -> GraphNeighborhood {
    let topology = topology(snapshot);
    if !topology.node_set.contains(center) {
        return GraphNeighborhood {
            center: center.clone(),
            node_ids: Vec::new(),
            edge_count: 0,
        };
    }

    let mut visited = BTreeSet::from([center.clone()]);
    let mut queue = VecDeque::from([(center.clone(), 0usize)]);

    while let Some((node, distance)) = queue.pop_front() {
        if distance >= depth {
            continue;
        }

        let neighbors = topology.outgoing[&node]
            .iter()
            .chain(topology.incoming[&node].iter())
            .cloned()
            .collect::<BTreeSet<_>>();
        for neighbor in neighbors {
            if visited.insert(neighbor.clone()) {
                queue.push_back((neighbor, distance + 1));
            }
        }
    }

    let edge_count = topology
        .edges
        .iter()
        .filter(|edge| visited.contains(&edge.from) && visited.contains(&edge.to))
        .count();

    GraphNeighborhood {
        center: center.clone(),
        node_ids: visited.into_iter().collect(),
        edge_count,
    }
}