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, PathBuf};

use once_cell::sync::Lazy;
use regex::Regex;
use rustc_hash::{FxHashMap, FxHashSet};

use crate::config::edge_weights::HELM;
use crate::types::{Fragment, FragmentId};

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

static HELM_VALUES_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"\{\{-?\s*\.Values\.([a-zA-Z0-9_.]+)").unwrap());
static HELM_INCLUDE_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r#"\{\{\s*(?:include|template)\s+"([^"]+)""#).unwrap());
static HELM_DEFINE_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r#"\{\{-?\s*define\s+"([^"]+)""#).unwrap());
static YAML_KEY_PATH_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^(\s*)([a-zA-Z_][a-zA-Z0-9_-]*):").unwrap());

fn is_helm_template(path: &Path) -> bool {
    let path_str = path.to_string_lossy();
    if !path_str.contains("templates") {
        return false;
    }
    let ext = base::file_ext(path);
    ext == ".yaml" || ext == ".yml" || ext == ".tpl"
}

fn is_helm_values(path: &Path) -> bool {
    let name = path
        .file_name()
        .map(|n| n.to_string_lossy().to_lowercase())
        .unwrap_or_default();
    name == "values.yaml"
        || name == "values.yml"
        || name.starts_with("values-")
        || name.starts_with("values_")
}

fn is_chart_yaml(path: &Path) -> bool {
    let name = path
        .file_name()
        .map(|n| n.to_string_lossy().to_lowercase())
        .unwrap_or_default();
    name == "chart.yaml" || name == "chart.yml"
}

fn extract_yaml_keys(content: &str, max_depth: usize) -> FxHashSet<String> {
    let mut keys = FxHashSet::default();
    let mut path_stack: Vec<(usize, String)> = Vec::new();

    for cap in YAML_KEY_PATH_RE.captures_iter(content) {
        let indent = cap[1].len();
        let key = cap[2].to_string();

        while let Some(last) = path_stack.last() {
            if last.0 >= indent {
                path_stack.pop();
            } else {
                break;
            }
        }

        path_stack.push((indent, key.clone()));

        if path_stack.len() <= max_depth {
            let full_path: String = path_stack
                .iter()
                .map(|(_, k)| k.as_str())
                .collect::<Vec<_>>()
                .join(".");
            keys.insert(full_path);
            keys.insert(key);
        }
    }

    keys
}

fn get_chart_root(path: &Path) -> Option<PathBuf> {
    let mut current = path.parent()?.to_path_buf();
    for _ in 0..5 {
        if current.join("Chart.yaml").exists() || current.join("chart.yaml").exists() {
            return Some(current);
        }
        let parent = current.parent()?;
        if parent == current {
            break;
        }
        current = parent.to_path_buf();
    }
    None
}

fn collect_chart_roots(paths: &[&PathBuf]) -> FxHashSet<PathBuf> {
    let mut roots = FxHashSet::default();
    for p in paths {
        if let Some(root) = get_chart_root(p) {
            roots.insert(root);
        }
    }
    roots
}

fn is_in_chart(candidate: &Path, chart_roots: &FxHashSet<PathBuf>) -> bool {
    for chart_root in chart_roots {
        if candidate.starts_with(chart_root) {
            return true;
        }
    }
    false
}

fn get_chart_root_from_fragment(path: &Path) -> Option<PathBuf> {
    get_chart_root(path)
}

fn build_values_index(
    values_files: &[&Fragment],
) -> FxHashMap<PathBuf, FxHashMap<String, Vec<FragmentId>>> {
    let mut idx: FxHashMap<PathBuf, FxHashMap<String, Vec<FragmentId>>> = FxHashMap::default();
    for vf in values_files {
        let path = Path::new(vf.path());
        let chart_root = get_chart_root_from_fragment(path)
            .unwrap_or_else(|| path.parent().unwrap_or(Path::new("")).to_path_buf());
        let chart_entry = idx.entry(chart_root).or_default();
        for key in extract_yaml_keys(&vf.content, 4) {
            chart_entry.entry(key).or_default().push(vf.id.clone());
        }
    }
    idx
}

fn build_define_index(templates: &[&Fragment]) -> FxHashMap<String, Vec<FragmentId>> {
    let mut defs: FxHashMap<String, Vec<FragmentId>> = FxHashMap::default();
    for tmpl in templates {
        for cap in HELM_DEFINE_RE.captures_iter(&tmpl.content) {
            defs.entry(cap[1].to_string())
                .or_default()
                .push(tmpl.id.clone());
        }
    }
    defs
}

fn link_longest_match(
    tmpl_id: &FragmentId,
    parts: &[&str],
    values_keys: &FxHashMap<String, Vec<FragmentId>>,
    edges: &mut EdgeDict,
) {
    for i in (1..=parts.len()).rev() {
        let partial = parts[..i].join(".");
        if let Some(values_ids) = values_keys.get(&partial) {
            if let Some(first) = values_ids.first() {
                add_edge(edges, tmpl_id, first, HELM.weight, HELM.reverse_factor);
                return;
            }
        }
    }
}

fn link_root_key(
    tmpl_id: &FragmentId,
    root_key: &str,
    values_keys: &FxHashMap<String, Vec<FragmentId>>,
    edges: &mut EdgeDict,
) {
    if let Some(values_ids) = values_keys.get(root_key) {
        for vid in values_ids {
            add_edge(
                edges,
                tmpl_id,
                vid,
                HELM.weight * HELM.value_modifier,
                HELM.reverse_factor,
            );
        }
    }
}

fn add_values_ref_edges(
    tmpl: &Fragment,
    values_keys: &FxHashMap<String, Vec<FragmentId>>,
    edges: &mut EdgeDict,
) {
    for cap in HELM_VALUES_RE.captures_iter(&tmpl.content) {
        let ref_str = &cap[1];
        let parts: Vec<&str> = ref_str.split('.').collect();
        link_longest_match(&tmpl.id, &parts, values_keys, edges);
        if let Some(root) = parts.first() {
            link_root_key(&tmpl.id, root, values_keys, edges);
        }
    }
}

fn add_include_edges(
    tmpl: &Fragment,
    define_defs: &FxHashMap<String, Vec<FragmentId>>,
    edges: &mut EdgeDict,
) {
    for cap in HELM_INCLUDE_RE.captures_iter(&tmpl.content) {
        if let Some(def_ids) = define_defs.get(&cap[1]) {
            for def_id in def_ids {
                if *def_id != tmpl.id {
                    add_edge(
                        edges,
                        &tmpl.id,
                        def_id,
                        HELM.weight * HELM.definition_modifier,
                        HELM.reverse_factor,
                    );
                }
            }
        }
    }
}

fn add_chart_file_edges(
    tmpl: &Fragment,
    chart_root: &Path,
    chart_files: &[&Fragment],
    edges: &mut EdgeDict,
) {
    for cf in chart_files {
        let cf_root = get_chart_root_from_fragment(Path::new(cf.path())).unwrap_or_else(|| {
            Path::new(cf.path())
                .parent()
                .unwrap_or(Path::new(""))
                .to_path_buf()
        });
        if cf_root == chart_root {
            add_edge(
                edges,
                &tmpl.id,
                &cf.id,
                HELM.weight * HELM.configmap_modifier,
                HELM.reverse_factor,
            );
        }
    }
}

fn add_template_edges(
    tmpl: &Fragment,
    values_idx: &FxHashMap<PathBuf, FxHashMap<String, Vec<FragmentId>>>,
    define_defs: &FxHashMap<String, Vec<FragmentId>>,
    chart_files: &[&Fragment],
    edges: &mut EdgeDict,
) {
    let tmpl_path = Path::new(tmpl.path());
    let chart_root = get_chart_root_from_fragment(tmpl_path).unwrap_or_else(|| {
        tmpl_path
            .parent()
            .and_then(|p| p.parent())
            .unwrap_or(Path::new(""))
            .to_path_buf()
    });

    let empty_keys = FxHashMap::default();
    let values_keys = values_idx.get(&chart_root).unwrap_or(&empty_keys);

    add_values_ref_edges(tmpl, values_keys, edges);
    add_include_edges(tmpl, define_defs, edges);
    add_chart_file_edges(tmpl, &chart_root, chart_files, edges);
}

pub struct HelmEdgeBuilder;

impl EdgeBuilder for HelmEdgeBuilder {
    fn build(&self, fragments: &[Fragment], _repo_root: Option<&Path>) -> EdgeDict {
        let templates: Vec<&Fragment> = fragments
            .iter()
            .filter(|f| is_helm_template(Path::new(f.path())))
            .collect();
        let values_files: Vec<&Fragment> = fragments
            .iter()
            .filter(|f| is_helm_values(Path::new(f.path())))
            .collect();
        let chart_files: Vec<&Fragment> = fragments
            .iter()
            .filter(|f| is_chart_yaml(Path::new(f.path())))
            .collect();

        if templates.is_empty() && values_files.is_empty() {
            return FxHashMap::default();
        }

        let mut edges: EdgeDict = FxHashMap::default();
        let values_idx = build_values_index(&values_files);
        let define_defs = build_define_index(&templates);

        for tmpl in &templates {
            add_template_edges(tmpl, &values_idx, &define_defs, &chart_files, &mut edges);
        }

        edges
    }

    fn discover_related_files(
        &self,
        changed: &[PathBuf],
        candidates: &[PathBuf],
        _repo_root: Option<&Path>,
        _file_cache: Option<&FxHashMap<PathBuf, String>>,
    ) -> Vec<PathBuf> {
        let templates: Vec<&PathBuf> = changed.iter().filter(|f| is_helm_template(f)).collect();
        let values: Vec<&PathBuf> = changed.iter().filter(|f| is_helm_values(f)).collect();

        if templates.is_empty() && values.is_empty() {
            return vec![];
        }

        let all_helm: Vec<&PathBuf> = templates.iter().chain(values.iter()).copied().collect();
        let chart_roots = collect_chart_roots(&all_helm);
        if chart_roots.is_empty() {
            return vec![];
        }

        let changed_set: FxHashSet<&PathBuf> = changed.iter().collect();
        let mut discovered = Vec::new();

        for candidate in candidates {
            if changed_set.contains(candidate) {
                continue;
            }
            if !is_helm_template(candidate)
                && !is_helm_values(candidate)
                && !is_chart_yaml(candidate)
            {
                continue;
            }
            if is_in_chart(candidate, &chart_roots) {
                discovered.push(candidate.clone());
            }
        }

        discovered
    }
}