atman-runtime 1.9.1

atman flow execution runtime: evaluator, tool dispatch, provider dispatch, executor, memory stores
Documentation
use atman_dsl::parse::parse_file;
use atman_runtime::{Executor, Value, tools};

fn base_flow() -> &'static str {
    r#"flow apply_all(file: path, new_content: string) -> HunkResult {
    contract { scope { read: [project_root] write: [project_root] } }
    proposal = hunk.plan_edit(file, new_content)
    return hunk.apply(proposal, hunks: "all")
}

flow apply_none(file: path, new_content: string) -> HunkResult {
    contract { scope { read: [project_root] write: [project_root] } }
    proposal = hunk.plan_edit(file, new_content)
    return hunk.apply(proposal, hunks: "none")
}

flow apply_first_only(file: path, new_content: string) -> HunkResult {
    contract { scope { read: [project_root] write: [project_root] } }
    proposal = hunk.plan_edit(file, new_content)
    return hunk.apply(proposal, hunks: [1])
}
"#
}

#[tokio::test]
async fn hunk_all_writes_full_proposed() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("x.txt");
    std::fs::write(&path, "a\nb\nc\n").unwrap();

    let file = parse_file(base_flow()).unwrap();
    let ex = Executor::new();
    tools::register_tier_zero(&ex.tools);
    let out = ex
        .run(
            &file,
            "apply_all",
            vec![
                ("file".into(), Value::Path(path.clone())),
                ("new_content".into(), Value::Str("a\nB\nc\n".into())),
            ],
        )
        .await
        .unwrap();
    let Value::Struct(fields) = out else {
        panic!("expected struct");
    };
    let f = |k: &str| fields.iter().find(|(n, _)| n == k).map(|(_, v)| v.clone());
    assert!(matches!(f("status"), Some(Value::Str(s)) if s == "applied"));
    assert_eq!(std::fs::read_to_string(&path).unwrap(), "a\nB\nc\n");
}

#[tokio::test]
async fn hunk_none_leaves_file_original() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("x.txt");
    std::fs::write(&path, "a\nb\nc\n").unwrap();

    let file = parse_file(base_flow()).unwrap();
    let ex = Executor::new();
    tools::register_tier_zero(&ex.tools);
    ex.run(
        &file,
        "apply_none",
        vec![
            ("file".into(), Value::Path(path.clone())),
            ("new_content".into(), Value::Str("a\nB\nc\n".into())),
        ],
    )
    .await
    .unwrap();
    assert_eq!(std::fs::read_to_string(&path).unwrap(), "a\nb\nc\n");
}

#[tokio::test]
async fn hunk_list_selection_applies_only_id_1() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("x.txt");
    let original: String = (0..20).map(|i| format!("l{i}\n")).collect();
    std::fs::write(&path, &original).unwrap();
    let mut proposed = original.clone();
    proposed = proposed.replace("l3\n", "L3\n");
    proposed = proposed.replace("l15\n", "L15\n");

    let file = parse_file(base_flow()).unwrap();
    let ex = Executor::new();
    tools::register_tier_zero(&ex.tools);
    ex.run(
        &file,
        "apply_first_only",
        vec![
            ("file".into(), Value::Path(path.clone())),
            ("new_content".into(), Value::Str(proposed)),
        ],
    )
    .await
    .unwrap();
    let on_disk = std::fs::read_to_string(&path).unwrap();
    assert!(on_disk.contains("L3\n"), "hunk 1 must be applied");
    assert!(!on_disk.contains("L15\n"), "hunk 2 must NOT be applied");
    assert!(on_disk.contains("l15\n"), "hunk 2 original must remain");
}

#[test]
fn examples_hunk_review_at_parses() {
    let src = std::fs::read_to_string("../../examples/hunk_review.at").unwrap();
    let file = parse_file(&src).unwrap();
    assert_eq!(file.flows.len(), 1);
    assert_eq!(file.flows[0].name.name, "hunk_review");
}