diffctx 1.12.1

Selects the minimum code an LLM needs to review a git diff: walks the dependency graph outward from changed lines and stops when extra context stops paying for itself
Documentation
use std::path::Path;

use rustc_hash::FxHashMap;

use crate::config::limits::SIBLING;
use crate::config::weights::EDGE_WEIGHTS;
use crate::types::{Fragment, FragmentId};

use super::super::EdgeDict;
use super::super::base::{EdgeBuilder, add_edge};

pub struct SiblingEdgeBuilder;

impl SiblingEdgeBuilder {
    fn group_files_by_dir<'a>(&self, fragments: &'a [Fragment]) -> FxHashMap<String, Vec<&'a str>> {
        let mut by_dir: FxHashMap<String, Vec<&str>> = FxHashMap::default();
        for f in fragments {
            let path = Path::new(f.path());
            let dir = path
                .parent()
                .map(|p| p.to_string_lossy().to_string())
                .unwrap_or_default();
            let path_str = f.path();
            let files = by_dir.entry(dir).or_default();
            if !files.contains(&path_str) {
                files.push(path_str);
            }
        }
        by_dir
    }

    fn build_file_representative_map(
        &self,
        fragments: &[Fragment],
    ) -> FxHashMap<String, FragmentId> {
        let mut file_to_rep: FxHashMap<String, FragmentId> = FxHashMap::default();
        let mut file_to_token_count: FxHashMap<String, u32> = FxHashMap::default();

        for f in fragments {
            let path = f.path().to_string();
            let existing_count = file_to_token_count.get(&path).copied().unwrap_or(0);
            if !file_to_rep.contains_key(&path) || f.token_count > existing_count {
                file_to_rep.insert(path.clone(), f.id.clone());
                file_to_token_count.insert(path, f.token_count);
            }
        }

        file_to_rep
    }
}

impl EdgeBuilder for SiblingEdgeBuilder {
    fn build(&self, fragments: &[Fragment], _repo_root: Option<&Path>) -> EdgeDict {
        let weight = EDGE_WEIGHTS["sibling"].forward;
        let reverse_factor = EDGE_WEIGHTS["sibling"].reverse_factor;

        let by_dir = self.group_files_by_dir(fragments);
        let file_to_rep = self.build_file_representative_map(fragments);

        let mut edges: EdgeDict = FxHashMap::default();

        for (_dir, files) in &by_dir {
            let mut file_list: Vec<&str> = files.clone();
            file_list.sort_unstable();
            if file_list.len() > SIBLING.max_files_per_dir {
                file_list.truncate(SIBLING.max_files_per_dir);
            }
            if file_list.len() < 2 {
                continue;
            }

            for i in 0..file_list.len() {
                for j in (i + 1)..file_list.len() {
                    if let (Some(f1_id), Some(f2_id)) =
                        (file_to_rep.get(file_list[i]), file_to_rep.get(file_list[j]))
                    {
                        add_edge(&mut edges, f1_id, f2_id, weight, reverse_factor);
                    }
                }
            }
        }

        edges
    }

    fn category_label(&self) -> Option<&str> {
        Some("sibling")
    }
}