selfware 0.6.2

Your personal AI workshop — software you own, software that lasts
use selfware::evolve::ast::{AstAnalyzer, AstNode};
use selfware::evolve::summary::{compile_structural_summary, compile_summary};

#[test]
fn test_structural_summary_projects_source_without_function_bodies() {
    let source = r#"#[derive(Debug)]
struct Item {
    value: usize,
}

impl<const N: usize> Item<{ N }> {
    /// Returns the stored value.
    fn value(&self) -> usize {
        self.value + N
    }
}
"#;
    let ast = AstAnalyzer::new().parse_source(source).unwrap();

    let summary = compile_summary(&ast, source);

    assert!(summary.contains("#[derive(Debug)]"));
    assert!(summary.contains("struct Item {"));
    assert!(summary.contains("value: usize"));
    assert!(summary.contains("impl<const N: usize> Item<{ N }> {"));
    assert!(summary.contains("/// Returns the stored value."));
    assert!(summary.contains("fn value(&self) -> usize { ... }"));
    assert!(!summary.contains("self.value + N"));
}

#[test]
fn test_structural_summary_skips_invalid_ranges_without_panicking() {
    let ast = AstNode {
        kind: "source_file".to_string(),
        start_byte: 0,
        end_byte: 2,
        start_line: 1,
        start_column: 1,
        end_line: 1,
        end_column: 3,
        label: None,
        named: true,
        has_error: false,
        children: vec![AstNode {
            kind: "function_item".to_string(),
            start_byte: 10,
            end_byte: 20,
            start_line: 1,
            start_column: 1,
            end_line: 1,
            end_column: 1,
            label: Some("bad".to_string()),
            named: true,
            has_error: false,
            children: vec![],
        }],
    };

    assert_eq!(compile_summary(&ast, "fn"), "");
}

#[test]
fn test_structural_summary_preserves_every_declaration_shape() {
    let source = r#"struct Pair<T>(T) where T: Copy;

trait Service {
    fn required(&self);
}

extern "C" {
    fn external(value: i32);
}

register_service!(Service);
"#;
    let ast = AstAnalyzer::new().parse_source(source).unwrap();

    let report = compile_structural_summary(&ast, source);

    assert!(report.outline.contains("struct Pair<T>(T) where T: Copy;"));
    assert!(!report.outline.contains("struct Pair<T> { ... }"));
    assert!(report.outline.contains("fn required(&self);"));
    assert!(report.outline.contains("extern \"C\" {"));
    assert!(report.outline.contains("fn external(value: i32);"));
    assert!(report.outline.contains("register_service!(Service);"));
    assert!(report.complete, "{report:#?}");
    assert!(report.omitted_node_kinds.is_empty());
    assert!(report.evidence.iter().all(|item| item.start_line > 0));
}

#[test]
fn test_structural_summary_discloses_parse_errors() {
    let source = "pub fn broken( {\n";
    let ast = AstAnalyzer::new().parse_source(source).unwrap();

    let report = compile_structural_summary(&ast, source);

    assert!(report.parse_has_error);
    assert!(!report.complete);
}

#[test]
fn test_structural_summary_reports_output_and_evidence_limits() {
    let declarations = (0..4097)
        .map(|index| format!("const VALUE_{index}: usize = {index};\n"))
        .collect::<String>();
    let ast = AstAnalyzer::new().parse_source(&declarations).unwrap();
    let evidence_limited = compile_structural_summary(&ast, &declarations);
    assert_eq!(evidence_limited.evidence.len(), 4096);
    assert!(evidence_limited.truncated);
    assert!(!evidence_limited.complete);

    let oversized = format!("const VALUE: &str = \"{}\";", "x".repeat(300_000));
    let ast = AstAnalyzer::new().parse_source(&oversized).unwrap();
    let output_limited = compile_structural_summary(&ast, &oversized);
    assert!(output_limited.truncated);
    assert!(!output_limited.complete);
    assert!(output_limited.outline.len() <= 256 * 1024);
}

#[test]
fn test_structural_summary_reports_nesting_limit() {
    let mut source = String::new();
    for index in 0..130 {
        source.push_str(&format!("mod level_{index} {{\n"));
    }
    source.push_str("const VALUE: usize = 1;\n");
    source.push_str(&"}\n".repeat(130));
    let ast = AstAnalyzer::new().parse_source(&source).unwrap();

    let report = compile_structural_summary(&ast, &source);

    assert!(report.depth_limit_reached);
    assert!(report.truncated);
    assert!(!report.complete);
}