cgz 2026.5.4

Local-first semantic code intelligence graph
Documentation
use serde_json::Value;
use std::fs;
use std::process::Command;
use tempfile::TempDir;

fn run(args: &[&str]) -> String {
    let bin = env!("CARGO_BIN_EXE_cgz");
    let output = Command::new(bin)
        .args(args)
        .output()
        .expect("failed to run codegraph");
    assert!(
        output.status.success(),
        "codegraph failed\nstdout:\n{}\nstderr:\n{}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
    String::from_utf8(output.stdout).unwrap()
}

fn query(project: &str, term: &str) -> Vec<(String, String, i64)> {
    let value: Value =
        serde_json::from_str(&run(&["query", term, "--path", project, "--json"])).unwrap();
    value
        .as_array()
        .unwrap()
        .iter()
        .map(|r| {
            let node = &r["node"];
            (
                node["kind"].as_str().unwrap().to_string(),
                node["name"].as_str().unwrap().to_string(),
                node["start_line"].as_i64().unwrap(),
            )
        })
        .collect()
}

#[test]
fn moonbit_tree_sitter_extracts_symbols_and_metadata() {
    let dir = TempDir::new().unwrap();
    fs::write(
        dir.path().join("moon.mod.json"),
        r#"{"name":"example/graph"}"#,
    )
    .unwrap();
    fs::write(dir.path().join("moon.pkg.json"), "{}").unwrap();
    fs::write(
        dir.path().join("lib.mbt"),
        r#"
pub struct User {
  id : String
}

pub enum Status {
  Active
  Disabled
}

pub trait Repository {
  find(Self, String) -> User?
}

pub fn process_data(input : String) -> String {
  helper(input)
}

fn helper(input : String) -> String {
  input
}

impl Repository for User with find(self, id) {
  None
}
"#,
    )
    .unwrap();
    fs::write(
        dir.path().join("README.mbt.md"),
        r#"# Example

```mbt
pub fn documented(value : String) -> String {
  value
}
```
"#,
    )
    .unwrap();

    let project = dir.path().to_str().unwrap();
    run(&["init", project, "--index"]);

    let process = query(project, "process_data");
    assert!(process
        .iter()
        .any(|(kind, name, _)| kind == "function" && name == "process_data"));

    let find = query(project, "find");
    assert!(find
        .iter()
        .any(|(kind, name, _)| kind == "method" && name == "find"));

    let active = query(project, "Active");
    assert!(active
        .iter()
        .any(|(kind, name, _)| kind == "enum_member" && name == "Active"));

    let documented = query(project, "documented");
    assert!(documented
        .iter()
        .any(|(kind, name, line)| kind == "function" && name == "documented" && *line == 4));

    let module = query(project, "example/graph");
    assert!(module
        .iter()
        .any(|(kind, name, _)| kind == "module" && name == "example/graph"));
}

#[test]
fn context_extracts_symbol_terms_from_natural_language() {
    let dir = TempDir::new().unwrap();
    fs::write(
        dir.path().join("moon.mod.json"),
        r#"{"name":"example/calver"}"#,
    )
    .unwrap();
    fs::write(dir.path().join("moon.pkg.json"), "{}").unwrap();
    fs::write(
        dir.path().join("parse.mbt"),
        r#"
pub fn parse_with_scheme(input : String) -> String {
  input
}

pub fn parse(input : String) -> String {
  parse_with_scheme(input)
}
"#,
    )
    .unwrap();

    let project = dir.path().to_str().unwrap();
    run(&["init", project, "--index"]);

    let output = run(&[
        "context",
        "change parse_with_scheme validation for invalid scheme order",
        "--path",
        project,
    ]);
    assert!(output.contains("parse_with_scheme"), "{output}");
    assert!(output.contains("pub fn parse_with_scheme"), "{output}");
}

#[test]
fn context_guides_when_no_symbols_match() {
    let dir = TempDir::new().unwrap();
    fs::write(
        dir.path().join("moon.mod.json"),
        r#"{"name":"example/empty"}"#,
    )
    .unwrap();
    fs::write(dir.path().join("moon.pkg.json"), "{}").unwrap();
    fs::write(
        dir.path().join("lib.mbt"),
        "pub fn known_symbol() -> Int { 1 }\n",
    )
    .unwrap();

    let project = dir.path().to_str().unwrap();
    run(&["init", project, "--index"]);

    let output = run(&["context", "zzzz_no_matching_symbol", "--path", project]);
    assert!(
        output.contains("No matching symbols or files were found"),
        "{output}"
    );
    assert!(output.contains("cgz query --json"), "{output}");
}

#[test]
fn affected_includes_moonbit_same_package_tests() {
    let dir = TempDir::new().unwrap();
    fs::write(
        dir.path().join("moon.mod.json"),
        r#"{"name":"example/calver"}"#,
    )
    .unwrap();
    fs::write(dir.path().join("moon.pkg.json"), "{}").unwrap();
    fs::write(
        dir.path().join("parse.mbt"),
        "pub fn parse() -> Int { 1 }\n",
    )
    .unwrap();
    fs::write(
        dir.path().join("scheme.mbt"),
        "pub fn scheme() -> Int { 2 }\n",
    )
    .unwrap();
    fs::write(
        dir.path().join("parse_test.mbt"),
        "test { inspect(parse(), content=\"1\") }\n",
    )
    .unwrap();
    fs::write(
        dir.path().join("parse_wbtest.mbt"),
        "test { inspect(scheme(), content=\"2\") }\n",
    )
    .unwrap();
    fs::write(
        dir.path().join("README.mbt.md"),
        "# Example\n\n```mbt check\ntest { inspect(parse(), content=\"1\") }\n```\n",
    )
    .unwrap();
    fs::create_dir_all(dir.path().join("other")).unwrap();
    fs::write(dir.path().join("other/moon.pkg.json"), "{}").unwrap();
    fs::write(
        dir.path().join("other/other_test.mbt"),
        "test { inspect(1, content=\"1\") }\n",
    )
    .unwrap();

    let project = dir.path().to_str().unwrap();
    run(&["init", project, "--index"]);

    let output = run(&[
        "affected",
        "parse.mbt",
        "scheme.mbt",
        "--path",
        project,
        "--json",
    ]);
    let value: Value = serde_json::from_str(&output).unwrap();
    let tests = value["affectedTests"].as_array().unwrap();
    assert!(tests.iter().any(|v| v == "README.mbt.md"), "{output}");
    assert!(tests.iter().any(|v| v == "parse_test.mbt"), "{output}");
    assert!(tests.iter().any(|v| v == "parse_wbtest.mbt"), "{output}");
    assert!(
        !tests.iter().any(|v| v == "other/other_test.mbt"),
        "{output}"
    );
    assert!(
        value["debug"].as_array().unwrap().iter().any(|entry| {
            entry["reason"]
                .as_str()
                .unwrap()
                .contains("MoonBit same-package")
        }),
        "{output}"
    );
}

#[test]
fn affected_keeps_direct_moonbit_test_input() {
    let dir = TempDir::new().unwrap();
    fs::write(
        dir.path().join("moon.mod.json"),
        r#"{"name":"example/calver"}"#,
    )
    .unwrap();
    fs::write(dir.path().join("moon.pkg.json"), "{}").unwrap();
    fs::write(
        dir.path().join("parse_test.mbt"),
        "test { inspect(1, content=\"1\") }\n",
    )
    .unwrap();

    let project = dir.path().to_str().unwrap();
    run(&["init", project, "--index"]);

    let output = run(&["affected", "parse_test.mbt", "--path", project, "--json"]);
    let value: Value = serde_json::from_str(&output).unwrap();
    let tests = value["affectedTests"].as_array().unwrap();
    assert_eq!(tests, &[Value::String("parse_test.mbt".into())]);
}