lwc 0.17.10

Agent-driven proactive memory CLI for AI agents — autonomously recall, maintain, and evolve persistent, source-grounded knowledge across sessions.
use crate::error::{AppError, Result};
use similar::{Algorithm, TextDiff};
use std::{fmt::Write, time::Duration};

pub const MAX_DIFF_INPUT_BYTES: usize = 8 * 1024 * 1024;
pub const MAX_DIFF_INPUT_LINES: usize = 200_000;
pub const DEFAULT_DIFF_OUTPUT_CHARS: usize = 20_000;
pub const MAX_DIFF_OUTPUT_CHARS: usize = 100_000;

#[derive(Debug, PartialEq, Eq)]
pub struct RenderedDiff {
    pub text: String,
    pub returned_chars: usize,
    pub total_chars: usize,
    pub truncated: bool,
}

pub fn render_diff(
    from: &str,
    to: &str,
    from_label: &str,
    to_label: &str,
    max_chars: usize,
) -> Result<RenderedDiff> {
    if !(1..=MAX_DIFF_OUTPUT_CHARS).contains(&max_chars) {
        return Err(AppError::new(
            "invalid_limit",
            format!("max-chars must be between 1 and {MAX_DIFF_OUTPUT_CHARS}"),
        ));
    }
    validate_input("from", from)?;
    validate_input("to", to)?;
    if from == to {
        return Ok(RenderedDiff {
            text: String::new(),
            returned_chars: 0,
            total_chars: 0,
            truncated: false,
        });
    }

    let mut config = TextDiff::configure();
    config
        .algorithm(Algorithm::Myers)
        .timeout(Duration::from_secs(1));
    let diff = config.diff_lines(from, to);
    let from_label = escape_label(from_label);
    let to_label = escape_label(to_label);
    let complete = diff
        .unified_diff()
        .context_radius(3)
        .header(&from_label, &to_label)
        .to_string();
    let total_chars = complete.chars().count();
    let truncated = total_chars > max_chars;
    let text = if truncated {
        complete.chars().take(max_chars).collect()
    } else {
        complete
    };
    Ok(RenderedDiff {
        text,
        returned_chars: total_chars.min(max_chars),
        total_chars,
        truncated,
    })
}

fn validate_input(side: &str, input: &str) -> Result<()> {
    let bytes = input.len();
    if bytes > MAX_DIFF_INPUT_BYTES {
        return Err(AppError::new(
            "source_diff_too_large",
            format!(
                "{side} input is {bytes} bytes; maximum source diff input is {MAX_DIFF_INPUT_BYTES} bytes"
            ),
        ));
    }
    let lines = input.split_inclusive('\n').count();
    if lines > MAX_DIFF_INPUT_LINES {
        return Err(AppError::new(
            "source_diff_too_large",
            format!(
                "{side} input has {lines} lines; maximum source diff input is {MAX_DIFF_INPUT_LINES} lines"
            ),
        ));
    }
    Ok(())
}

fn escape_label(label: &str) -> String {
    let mut escaped = String::with_capacity(label.len());
    for character in label.chars() {
        match character {
            '\\' => escaped.push_str("\\\\"),
            '\n' => escaped.push_str("\\n"),
            '\r' => escaped.push_str("\\r"),
            '\t' => escaped.push_str("\\t"),
            '\u{0}'..='\u{1f}' | '\u{7f}' => {
                write!(escaped, "\\u{:04X}", character as u32).unwrap();
            }
            _ => escaped.push(character),
        }
    }
    escaped
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn renders_line_changes_with_safe_headers() {
        let rendered = render_diff(
            "first\nremove me\nport=80\nlast\n",
            "first\nport=81\ninsert me\nlast\n",
            "source:7",
            "live:a\\b\r\n\t\u{7f}中.md",
            MAX_DIFF_OUTPUT_CHARS,
        )
        .unwrap();

        assert!(
            rendered
                .text
                .starts_with("--- source:7\n+++ live:a\\\\b\\r\\n\\t\\u007F中.md\n")
        );
        assert!(rendered.text.contains("-remove me\n"));
        assert!(rendered.text.contains("-port=80\n+port=81\n"));
        assert!(rendered.text.contains("+insert me\n"));
        assert_eq!(rendered.returned_chars, rendered.text.chars().count());
        assert_eq!(rendered.total_chars, rendered.returned_chars);
        assert!(!rendered.truncated);
    }

    #[test]
    fn truncates_only_at_a_unicode_scalar_boundary() {
        let complete = render_diff(
            "甲乙丙丁\n",
            "甲乙😀丁\n",
            "source:1",
            "live:中文.md",
            MAX_DIFF_OUTPUT_CHARS,
        )
        .unwrap();
        assert!(complete.text.contains("-甲乙丙丁\n+甲乙😀丁\n"));
        assert!(!complete.truncated);

        let rendered =
            render_diff("甲乙丙丁\n", "甲乙😀丁\n", "source:1", "live:中文.md", 17).unwrap();

        assert_eq!(rendered.text.chars().count(), 17);
        assert_eq!(rendered.returned_chars, 17);
        assert!(rendered.total_chars > rendered.returned_chars);
        assert!(rendered.truncated);
        assert!(rendered.text.is_char_boundary(rendered.text.len()));
    }

    #[test]
    fn rejects_inputs_above_the_fixed_byte_and_line_caps() {
        let byte_boundary = "x".repeat(MAX_DIFF_INPUT_BYTES);
        assert_eq!(
            render_diff(&byte_boundary, &byte_boundary, "source:1", "source:1", 1)
                .unwrap()
                .text,
            ""
        );
        let oversized = "x".repeat(MAX_DIFF_INPUT_BYTES + 1);
        let error = render_diff(&oversized, "", "source:1", "source:2", 1).unwrap_err();
        assert_eq!(error.code, "source_diff_too_large");

        let line_boundary = "\n".repeat(MAX_DIFF_INPUT_LINES);
        assert_eq!(
            render_diff(&line_boundary, &line_boundary, "source:1", "source:1", 1)
                .unwrap()
                .text,
            ""
        );
        let too_many_lines = "\n".repeat(MAX_DIFF_INPUT_LINES + 1);
        let error = render_diff(&too_many_lines, "", "source:1", "source:2", 1).unwrap_err();
        assert_eq!(error.code, "source_diff_too_large");
    }
}