a3s-code-core 5.2.3

A3S Code Core - Embeddable AI agent library with tool execution
Documentation
use super::*;
use std::process::Command;

fn write(path: &Path, body: &[u8]) {
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent).unwrap();
    }
    std::fs::write(path, body).unwrap();
}

fn git_available() -> bool {
    Command::new("git").arg("--version").output().is_ok()
}

fn run_git(root: &Path, args: &[&str]) {
    let status = Command::new("git")
        .arg("-C")
        .arg(root)
        .args(args)
        .status()
        .unwrap();
    assert!(
        status.success(),
        "git command failed: git -C {root:?} {args:?}"
    );
}

fn test_file(path: &str) -> LocalWorkspaceFile {
    LocalWorkspaceFile {
        path: path.to_string(),
        size: 0,
        modified_ms: None,
        language: None,
        status: LocalWorkspaceFileStatus::Unknown,
        binary: false,
        generated: false,
    }
}

#[test]
fn manifest_index_reduces_glob_candidates() {
    let files = vec![
        test_file("src/main.rs"),
        test_file("crates/demo/src/lib.rs"),
        test_file("README.md"),
        test_file("docs/README.md"),
    ];
    let index = ManifestIndex::build(&files);

    let exact = candidate_indices_for_glob(&index, &WorkspacePath::root(), "src/main.rs")
        .iter()
        .collect::<Vec<_>>();
    assert_eq!(exact, vec![0]);

    let basename = candidate_indices_for_glob(&index, &WorkspacePath::root(), "**/README.md")
        .iter()
        .collect::<Vec<_>>();
    assert_eq!(basename, vec![2, 3]);

    let extension = candidate_indices_for_glob(&index, &WorkspacePath::root(), "**/*.rs")
        .iter()
        .collect::<Vec<_>>();
    assert_eq!(extension, vec![0, 1]);
}

#[test]
fn recent_files_are_bounded_and_ranked_by_heat() {
    let mut recent = RecentFiles::default();
    for index in 0..(RECENT_FILE_LIMIT + 5) {
        recent.touch(format!("src/file_{index:03}.rs"), index as u64);
    }
    recent.touch("src/file_000.rs".to_string(), 10_000);
    recent.touch("src/file_000.rs".to_string(), 10_001);

    let entries = recent.entries(None, usize::MAX, 10_001);

    assert_eq!(entries.len(), RECENT_FILE_LIMIT);
    assert_eq!(entries[0].path, "src/file_000.rs");
    assert_eq!(entries[0].touch_count, 2);
}

#[tokio::test]
async fn manifest_search_matches_glob_and_grep() {
    let temp = tempfile::tempdir().unwrap();
    write(
        &temp.path().join("src/main.rs"),
        b"fn main() {\n    println!(\"hello\");\n}\n",
    );
    write(&temp.path().join("README.md"), b"hello from docs\n");

    let backend = ManifestWorkspaceBackend::new(temp.path());
    let mut rx = backend.manifest().subscribe();
    tokio::time::timeout(Duration::from_secs(5), rx.recv())
        .await
        .unwrap()
        .unwrap();

    let glob = backend
        .glob(WorkspaceGlobRequest {
            base: backend.normalize("src").unwrap(),
            pattern: "*.rs".to_string(),
        })
        .await
        .unwrap();
    assert_eq!(glob.matches[0].as_str(), "src/main.rs");

    let grep = backend
        .grep(WorkspaceGrepRequest {
            base: WorkspacePath::root(),
            pattern: "hello".to_string(),
            glob: Some("*.rs".to_string()),
            context_lines: 0,
            case_insensitive: false,
            max_output_size: 1024,
        })
        .await
        .unwrap();
    assert_eq!(grep.match_count, 1);
    assert_eq!(grep.file_count, 1);
    assert!(grep.output.contains("src/main.rs:2"));
}

#[tokio::test]
async fn manifest_backend_read_write_touch_recent_files() {
    let temp = tempfile::tempdir().unwrap();
    write(&temp.path().join("src/main.rs"), b"fn main() {}\n");

    let backend = ManifestWorkspaceBackend::new(temp.path());
    let mut rx = backend.manifest().subscribe();
    tokio::time::timeout(Duration::from_secs(5), rx.recv())
        .await
        .unwrap()
        .unwrap();

    let path = backend.normalize("src/main.rs").unwrap();
    backend.read_text(&path).await.unwrap();
    assert_eq!(
        backend.manifest().recent_file_paths(4),
        vec!["src/main.rs".to_string()]
    );

    backend
        .write_text(&path, "fn main() { println!(\"hi\"); }\n")
        .await
        .unwrap();
    let entries = backend.manifest().recent_file_entries(4);
    assert_eq!(entries[0].path, "src/main.rs");
    assert_eq!(entries[0].touch_count, 2);
}

#[tokio::test]
async fn manifest_glob_prioritizes_recent_matching_files() {
    let temp = tempfile::tempdir().unwrap();
    write(&temp.path().join("src/a.rs"), b"pub fn a() {}\n");
    write(&temp.path().join("src/z.rs"), b"pub fn z() {}\n");

    let backend = ManifestWorkspaceBackend::new(temp.path());
    let mut rx = backend.manifest().subscribe();
    tokio::time::timeout(Duration::from_secs(5), rx.recv())
        .await
        .unwrap()
        .unwrap();

    backend.manifest().touch_file("src/z.rs");
    let glob = backend
        .glob(WorkspaceGlobRequest {
            base: WorkspacePath::root(),
            pattern: "**/*.rs".to_string(),
        })
        .await
        .unwrap();

    assert_eq!(glob.matches[0].as_str(), "src/z.rs");
    assert_eq!(glob.matches[1].as_str(), "src/a.rs");
}

#[tokio::test]
async fn manifest_grep_prioritizes_recent_matches_when_truncated() {
    let temp = tempfile::tempdir().unwrap();
    write(
        &temp.path().join("src/a.rs"),
        b"pub const HIT: &str = \"a\";\n",
    );
    write(
        &temp.path().join("src/z.rs"),
        b"pub const HIT: &str = \"z\";\n",
    );

    let backend = ManifestWorkspaceBackend::new(temp.path());
    let mut rx = backend.manifest().subscribe();
    tokio::time::timeout(Duration::from_secs(5), rx.recv())
        .await
        .unwrap()
        .unwrap();

    backend.manifest().touch_file("src/z.rs");
    let grep = backend
        .grep(WorkspaceGrepRequest {
            base: WorkspacePath::root(),
            pattern: "HIT".to_string(),
            glob: Some("**/*.rs".to_string()),
            context_lines: 0,
            case_insensitive: false,
            max_output_size: 1,
        })
        .await
        .unwrap();

    assert!(grep.truncated);
    assert!(grep.output.starts_with(">src/z.rs:"), "{}", grep.output);
}

#[tokio::test]
async fn manifest_refreshes_after_file_event() {
    let temp = tempfile::tempdir().unwrap();
    write(&temp.path().join("README.md"), b"# hello\n");
    let manifest = LocalWorkspaceManifest::start(temp.path());
    let mut rx = manifest.subscribe();
    let initial = tokio::time::timeout(Duration::from_secs(5), rx.recv())
        .await
        .unwrap()
        .unwrap();
    assert!(initial.files.iter().any(|file| file.path == "README.md"));

    write(&temp.path().join("src/lib.rs"), b"pub fn lib() {}\n");
    let updated = tokio::time::timeout(Duration::from_secs(10), async {
        loop {
            let snapshot = rx.recv().await.unwrap();
            if snapshot.files.iter().any(|file| file.path == "src/lib.rs") {
                break snapshot;
            }
        }
    })
    .await
    .unwrap();

    assert!(updated.version > initial.version);
}

#[tokio::test]
async fn manifest_search_falls_back_before_initial_scan() {
    let temp = tempfile::tempdir().unwrap();
    write(&temp.path().join("src/main.rs"), b"fn main() {}\n");
    let backend = ManifestWorkspaceBackend::new(temp.path());
    let glob = backend
        .glob(WorkspaceGlobRequest {
            base: backend.normalize("src").unwrap(),
            pattern: "*.rs".to_string(),
        })
        .await
        .unwrap();
    assert!(glob
        .matches
        .iter()
        .any(|path| path.as_str() == "src/main.rs"));
}

#[test]
fn scan_includes_files_inside_nested_git_workspaces() {
    if !git_available() {
        return;
    }

    let temp = tempfile::tempdir().unwrap();
    run_git(temp.path(), &["init"]);
    write(&temp.path().join("README.md"), b"# root\n");

    let nested = temp.path().join("vendor/child");
    std::fs::create_dir_all(&nested).unwrap();
    run_git(&nested, &["init"]);
    write(&nested.join("src/lib.rs"), b"pub fn child() {}\n");

    let files = scan_workspace_files(temp.path());
    assert!(files.iter().any(|file| file.path == "README.md"));
    assert!(files
        .iter()
        .any(|file| file.path == "vendor/child/src/lib.rs"));
}