agent-file-tools 0.42.0

Agent File Tools — tree-sitter powered code analysis for AI agents
Documentation
use std::fs;

use serde_json::json;
use tempfile::tempdir;

use super::helpers::user_config;
use super::test_helpers::AftProcess;

fn configure(aft: &mut AftProcess, project: &std::path::Path, storage: &std::path::Path) {
    let response = aft.send(
        &json!({
            "id": "cfg",
            "command": "configure",
            "harness": "opencode",
            "project_root": project,
            "storage_dir": storage,
            "config": user_config(serde_json::json!({
                "experimental": { "bash": { "compress": true } },
                "search_index": false,
                "semantic_search": false
            }))
        })
        .to_string(),
    );
    assert_eq!(response["success"], true, "configure failed: {response}");
}

fn write_filter(dir: &std::path::Path, name: &str, body: &str) {
    fs::create_dir_all(dir).unwrap();
    fs::write(dir.join(format!("{name}.toml")), body).unwrap();
}

#[test]
fn list_filters_reports_builtin_user_project_and_invalid_untrusted() {
    let project = tempdir().unwrap();
    let storage = tempdir().unwrap();
    write_filter(
        &storage.path().join("filters"),
        "user-build",
        r#"[filter]
matches = ["user-build"]
description = "User build filter"
"#,
    );
    write_filter(
        &project.path().join(".cortexkit/aft/filters"),
        "internal-test",
        r#"[filter]
matches = ["internal-test"]
description = "Internal test filter"
"#,
    );
    write_filter(
        &project.path().join(".cortexkit/aft/filters"),
        "bad",
        "not valid = toml = at all =",
    );

    let mut aft = AftProcess::spawn();
    configure(&mut aft, project.path(), storage.path());
    let response = aft.send(&json!({ "id": "list", "command": "list_filters" }).to_string());

    assert_eq!(response["success"], true, "list failed: {response}");
    let filters = response["filters"].as_array().unwrap();
    assert!(filters
        .iter()
        .any(|f| f["source"] == "builtin" && f["name"] == "make"));
    assert!(filters
        .iter()
        .any(|f| f["source"] == "user" && f["name"] == "user-build"));
    assert_eq!(
        response["user_dir"].as_str().unwrap().replace('\\', "/"),
        storage
            .path()
            .join("opencode/filters")
            .display()
            .to_string()
            .replace('\\', "/")
    );
    assert!(storage
        .path()
        .join("opencode/filters/user-build.toml")
        .exists());
    assert!(!storage.path().join("filters/user-build.toml").exists());
    let project_filter = filters
        .iter()
        .find(|f| f["source"] == "project" && f["name"] == "internal-test")
        .unwrap();
    assert_eq!(project_filter["trusted"], false);
    let invalid = filters
        .iter()
        .find(|f| f["source"] == "project_invalid" && f["name"] == "bad")
        .unwrap();
    assert!(invalid["error"].as_str().unwrap().contains("invalid TOML"));
}

#[test]
fn trust_project_marks_current_project_trusted() {
    let project = tempdir().unwrap();
    let storage = tempdir().unwrap();
    write_filter(
        &project.path().join(".cortexkit/aft/filters"),
        "printf",
        r#"[filter]
matches = ["printf"]
description = "Replace printf output"

[shortcircuit]
when = "hello"
replacement = "project filter active"
"#,
    );
    let mut aft = AftProcess::spawn();
    configure(&mut aft, project.path(), storage.path());

    let trust = aft.send(
        &json!({ "id": "trust", "command": "trust_filter_project", "project_root": project.path() })
            .to_string(),
    );
    assert_eq!(trust["success"], true, "trust failed: {trust}");
    assert_eq!(trust["trusted"], true);

    let list = aft.send(&json!({ "id": "list", "command": "list_filters" }).to_string());
    let project_filter = list["filters"]
        .as_array()
        .unwrap()
        .iter()
        .find(|filter| filter["name"] == "printf")
        .unwrap();
    assert_eq!(project_filter["trusted"], true);
    assert_eq!(list["trusted_projects"].as_array().unwrap().len(), 1);
}

#[test]
fn untrust_project_removes_trusted_path() {
    let project = tempdir().unwrap();
    let storage = tempdir().unwrap();
    let mut aft = AftProcess::spawn();
    configure(&mut aft, project.path(), storage.path());

    let trust = aft.send(
        &json!({ "id": "trust", "command": "trust_filter_project", "project_root": project.path() })
            .to_string(),
    );
    assert_eq!(trust["success"], true);
    let untrust = aft.send(
        &json!({ "id": "untrust", "command": "untrust_filter_project", "project_root": project.path() })
            .to_string(),
    );
    assert_eq!(untrust["success"], true, "untrust failed: {untrust}");
    assert_eq!(untrust["trusted"], false);

    let list = aft.send(&json!({ "id": "list", "command": "list_filters" }).to_string());
    assert_eq!(list["trusted_projects"].as_array().unwrap().len(), 0);
}

#[test]
fn trust_project_errors_when_path_missing() {
    let project = tempdir().unwrap();
    let storage = tempdir().unwrap();
    let missing = project.path().join("missing");
    let mut aft = AftProcess::spawn();
    configure(&mut aft, project.path(), storage.path());

    let response = aft.send(
        &json!({ "id": "trust", "command": "trust_filter_project", "project_root": missing })
            .to_string(),
    );
    assert_eq!(response["success"], false);
    assert_eq!(response["code"], "path_not_found");
}