selfware 0.6.0

Your personal AI workshop — software you own, software that lasts
Documentation
use selfware::evolve::deletion::{preview_deletion, require_executable};
use selfware::evolve::{Edge, EdgeType, Graph, IdeEngine, Node};

fn deletion_fixture() -> (tempfile::TempDir, IdeEngine, Graph) {
    let project = tempfile::tempdir().unwrap();
    std::fs::create_dir_all(project.path().join("src")).unwrap();
    std::fs::write(project.path().join("src/target.rs"), "pub fn target() {}\n").unwrap();
    std::fs::write(
        project.path().join("src/dependent.rs"),
        "use crate::target::target;\n",
    )
    .unwrap();

    let graph = Graph {
        nodes: vec![
            Node::code("crate::dependent", "src/dependent.rs"),
            Node::code("crate::target", "src/target.rs"),
        ],
        edges: vec![
            Edge {
                from: "crate::dependent".to_string(),
                to: "crate::target".to_string(),
                edge_type: EdgeType::DependsOn,
            },
            Edge {
                from: "crate::target".to_string(),
                to: "crate::dependent".to_string(),
                edge_type: EdgeType::Influences,
            },
        ],
    };
    let ide = IdeEngine::for_project(project.path());
    (project, ide, graph)
}

#[test]
fn test_deletion_preview_is_exact_non_mutating_and_blocked() {
    let (project, ide, graph) = deletion_fixture();
    let before = std::fs::read_to_string(project.path().join("src/target.rs")).unwrap();
    let expected_hash = ide.read_document("src/target.rs").unwrap().hash;

    let preview = preview_deletion(&graph, &ide, "crate::target").unwrap();

    assert_eq!(preview.lifecycle, "proposed");
    assert_eq!(preview.action, "preview_source_deletion");
    assert_eq!(preview.target.logical_node_id, "crate::target");
    assert_eq!(preview.target.path, "src/target.rs");
    assert_eq!(preview.target.content_hash, expected_hash);
    assert_eq!(preview.target.graph_revision.len(), 64);
    assert_eq!(preview.removed_lines, 1);
    assert_eq!(preview.inbound.len(), 1);
    assert_eq!(preview.inbound[0].direction, "inbound");
    assert_eq!(preview.inbound[0].from, "crate::dependent");
    assert_eq!(preview.inbound[0].edge_type, "DependsOn");
    assert_eq!(preview.outbound.len(), 1);
    assert_eq!(preview.outbound[0].direction, "outbound");
    assert_eq!(preview.outbound[0].to, "crate::dependent");
    assert_eq!(preview.outbound[0].edge_type, "Influences");
    assert_eq!(preview.evidence_completeness, "partial");
    assert!(!preview.executable);
    assert!(preview
        .blockers
        .iter()
        .any(|blocker| blocker.contains("inbound graph relationship")));
    assert!(preview
        .blockers
        .iter()
        .any(|blocker| blocker.contains("reference completeness")));
    assert_eq!(
        preview
            .verification
            .iter()
            .map(|step| step.order)
            .collect::<Vec<_>>(),
        vec![1, 2, 3]
    );
    assert!(require_executable(&preview).is_err());
    assert_eq!(
        std::fs::read_to_string(project.path().join("src/target.rs")).unwrap(),
        before
    );
}

#[test]
fn test_deletion_preview_revision_is_stable_for_the_same_graph() {
    let (_project, ide, graph) = deletion_fixture();

    let first = preview_deletion(&graph, &ide, "crate::target").unwrap();
    let second = preview_deletion(&graph, &ide, "crate::target").unwrap();

    assert_ne!(first.action_id, second.action_id);
    assert_eq!(first.target.graph_revision, second.target.graph_revision);
    assert_eq!(first.target.content_hash, second.target.content_hash);
}

#[test]
fn test_deletion_preview_rejects_unknown_and_pathless_nodes() {
    let (_project, ide, mut graph) = deletion_fixture();
    graph.nodes.push(selfware::evolve::Node {
        id: "concept".to_string(),
        layer: selfware::evolve::NodeLayer::Concept,
        path: None,
        tokens: 0,
        lines: 0,
        files: 0,
        coverage: None,
        dead_code_ratio: None,
        warning_count: None,
        complexity: None,
        inline_test_ranges: 0,
        inline_test_lines: 0,
        inline_test_tokens: 0,
        classification: "concept".to_string(),
    });

    assert!(preview_deletion(&graph, &ide, "missing").is_err());
    assert!(preview_deletion(&graph, &ide, "concept").is_err());
}