gobby-code 1.3.3

Fast Rust CLI for Gobby's code index — AST-aware search, symbol navigation, and dependency graph
Documentation
use super::support::*;
use gobby_code::db;

#[test]
fn index_resolves_cross_file_local_cpp_calls() {
    let Some(env) = StandaloneEnv::from_env() else {
        eprintln!(
            "skipping cross-file C++ local-call resolution; set GCODE_GRAPH_STANDALONE_DATABASE_URL, GCODE_GRAPH_STANDALONE_FALKOR_HOST, and GCODE_GRAPH_STANDALONE_FALKOR_PORT"
        );
        return;
    };

    let project = tempfile::tempdir().expect("temp project");
    let include_dir = project.path().join("include");
    let src_dir = project.path().join("src");
    fs::create_dir_all(project.path().join(".gobby")).expect("create .gobby");
    fs::create_dir_all(&include_dir).expect("create include");
    fs::create_dir_all(&src_dir).expect("create src");

    let helper_path = include_dir.join("helper.hpp");
    let main_path = src_dir.join("main.cpp");
    fs::write(
        &helper_path,
        "#pragma once\n\ninline int helper() {\n    return 41;\n}\n",
    )
    .expect("write helper.hpp");
    fs::write(
        &main_path,
        "#include \"helper.hpp\"\n\nint run() {\n    return helper();\n}\n",
    )
    .expect("write main.cpp");
    fs::write(
        project.path().join("compile_commands.json"),
        serde_json::json!([{
            "directory": project.path().to_string_lossy(),
            "command": format!(
                "c++ -std=c++17 -I{} -c {}",
                include_dir.display(),
                main_path.display()
            ),
            "file": main_path.to_string_lossy()
        }])
        .to_string(),
    )
    .expect("write compile_commands.json");
    fs::write(
        project.path().join(".gobby/gcode.json"),
        serde_json::json!({
            "id": CPP_LOCAL_PROJECT_ID,
            "name": "graph-standalone-cpp-local",
            "created_at": "2026-06-16T00:00:00Z"
        })
        .to_string(),
    )
    .expect("write gcode identity");

    let mut conn = Client::connect(&env.database_url, NoTls).expect("connect PostgreSQL");
    cleanup_project(&mut conn, CPP_LOCAL_PROJECT_ID).expect("cleanup C++ project");
    let _cleanup = ProjectCleanup::new(&env.database_url, CPP_LOCAL_PROJECT_ID);

    let indexed = run_gcode(
        &env,
        project.path(),
        &["index", "--full", "--require-cpp-semantics"],
    );
    if cpp_semantics_unavailable(&indexed) {
        eprintln!(
            "skipping cross-file C++ local-call resolution; install clangd or set GCODE_CLANGD"
        );
        return;
    }
    assert_success(indexed, "gcode index --full --require-cpp-semantics");

    let helper_id = required_symbol_id(
        &mut conn,
        CPP_LOCAL_PROJECT_ID,
        "include/helper.hpp",
        "helper",
    );
    assert_eq!(
        resolved_call_target(&mut conn, CPP_LOCAL_PROJECT_ID, "src/main.cpp", "helper"),
        Some(helper_id.clone()),
        "cross-file C++ call did not resolve to the canonical helper symbol"
    );
    assert_eq!(
        pending_local_import_count(&mut conn, CPP_LOCAL_PROJECT_ID),
        0,
        "no local_import rows should survive a completed index run"
    );

    let rebuilt = json_command(&env, project.path(), &["graph", "rebuild"]);
    assert_eq!(rebuilt["success"], true);
    assert_caller_present(&env, project.path(), "helper", "run", "after rebuild");

    let mut graph = phantom_graph_client(&env);
    assert_eq!(
        phantom_call_target_count(&mut graph, CPP_LOCAL_PROJECT_ID),
        0,
        "no CALLS target may lack a DEFINES edge after rebuild"
    );
    assert!(
        resolved_target_is_defined_and_called(&mut graph, CPP_LOCAL_PROJECT_ID, &helper_id),
        "the resolved C++ target must be a defined, called CodeSymbol"
    );

    let blast = json_command(&env, project.path(), &["blast-radius", "helper"]);
    assert_blast_radius_reports_affected_callers(&blast);

    let sync = run_gcode(
        &env,
        project.path(),
        &["graph", "sync-file", "--file", "src/main.cpp"],
    );
    assert_success(sync, "graph sync-file src/main.cpp");
    assert_caller_present(&env, project.path(), "helper", "run", "after sync-file");
    assert_eq!(
        phantom_call_target_count(&mut graph, CPP_LOCAL_PROJECT_ID),
        0,
        "sync-file must not introduce a phantom CALLS target"
    );
}

#[test]
fn db_resolves_seeded_cpp_candidate_file_to_symbol_id() {
    let Some(env) = StandaloneEnv::from_env() else {
        eprintln!(
            "skipping seeded C++ local-callee DB resolution; set GCODE_GRAPH_STANDALONE_DATABASE_URL, GCODE_GRAPH_STANDALONE_FALKOR_HOST, and GCODE_GRAPH_STANDALONE_FALKOR_PORT"
        );
        return;
    };

    let mut conn = Client::connect(&env.database_url, NoTls).expect("connect PostgreSQL");
    cleanup_project(&mut conn, CPP_DB_LOCAL_PROJECT_ID).expect("cleanup C++ DB project");
    let _cleanup = ProjectCleanup::new(&env.database_url, CPP_DB_LOCAL_PROJECT_ID);
    conn.batch_execute(
        "INSERT INTO code_indexed_projects
            (id, root_path, total_files, total_symbols, last_indexed_at, index_duration_ms)
         VALUES
            ('graph-standalone-cpp-db-local', '/tmp/graph-standalone-cpp-db', 1, 1, NOW(), 0);

         INSERT INTO code_indexed_files
            (id, project_id, file_path, language, content_hash, symbol_count, byte_size,
             graph_synced, vectors_synced, graph_sync_attempted_at, indexed_at)
         VALUES
            ('graph-standalone-cpp-db-helper-file', 'graph-standalone-cpp-db-local',
             'include/helper.hpp', 'cpp', 'hash-cpp-helper', 1, 53, false, true, NULL, NOW());

         INSERT INTO code_symbols
            (id, project_id, file_path, name, qualified_name, kind, language, byte_start, byte_end,
             line_start, line_end, signature, docstring, parent_symbol_id, content_hash,
             summary, created_at, updated_at)
         VALUES
            ('graph-standalone-cpp-db-helper', 'graph-standalone-cpp-db-local',
             'include/helper.hpp', 'helper', 'helper', 'function', 'cpp', 14, 52,
             3, 5, 'inline int helper()', NULL, NULL, 'hash-cpp-helper', NULL, NOW(), NOW());",
    )
    .expect("seed C++ DB rows");

    let target_files = vec!["include/helper.hpp".to_string()];
    let resolved = db::resolve_local_callee_symbol_id(
        &mut conn,
        CPP_DB_LOCAL_PROJECT_ID,
        &target_files,
        "helper",
    )
    .expect("resolve seeded C++ local callee");
    assert_eq!(
        resolved.as_deref(),
        Some("graph-standalone-cpp-db-helper"),
        "seeded C++ candidate file should resolve to the indexed symbol id"
    );
}

fn cpp_semantics_unavailable(output: &Output) -> bool {
    if output.status.success() {
        return false;
    }
    let stderr = String::from_utf8_lossy(&output.stderr);
    stderr.contains("C/C++ semantic indexing requires clangd")
}