diff --git a/internal/cbm/cbm.h b/internal/cbm/cbm.h index 39c1da31..8049598c 100644 --- a/internal/cbm/cbm.h +++ b/internal/cbm/cbm.h @@ -173,6 +173,7 @@ typedef enum { typedef struct { const char *value; // the string literal content const char *enclosing_func_qn; // QN of enclosing function + const char *key_path; // dotted key path from YAML/JSON nesting (NULL if flat) CBMStringRefKind kind; // URL, CONFIG } CBMStringRef; diff --git a/internal/cbm/extract_unified.c b/internal/cbm/extract_unified.c index 1224355e..a967eca0 100644 --- a/internal/cbm/extract_unified.c +++ b/internal/cbm/extract_unified.c @@ -248,6 +248,116 @@ static void handle_string_refs(CBMExtractCtx *ctx, TSNode node, const WalkState cbm_stringref_push(&ctx->result->string_refs, ctx->arena, ref); } +// --- YAML nested field extraction (D2) --- + +/* Recursively walk YAML block_mapping_pair nodes, building dotted key paths. + * Emits string_refs with key_path for leaf values that are URLs or config values. + * Example: body.operational_info.post_url → "https://..." */ +static void walk_yaml_mapping(CBMExtractCtx *ctx, TSNode node, const char *prefix) { + uint32_t nc = ts_node_named_child_count(node); + for (uint32_t i = 0; i < nc; i++) { + TSNode child = ts_node_named_child(node, i); + const char *ck = ts_node_type(child); + + if (strcmp(ck, "block_mapping_pair") != 0) { + continue; + } + + /* Get key */ + TSNode key = ts_node_child_by_field_name(child, "key", 3); + if (ts_node_is_null(key)) { + continue; + } + char *key_text = cbm_node_text(ctx->arena, key, ctx->source); + if (!key_text || !key_text[0]) { + continue; + } + + /* Build dotted path */ + const char *path = + prefix ? cbm_arena_sprintf(ctx->arena, "%s.%s", prefix, key_text) : key_text; + + /* Get value node */ + TSNode val = ts_node_child_by_field_name(child, "value", 5); + if (ts_node_is_null(val)) { + continue; + } + const char *vk = ts_node_type(val); + + /* If value is a nested mapping (block_node → block_mapping), recurse */ + if (strcmp(vk, "block_node") == 0 || strcmp(vk, "block_mapping") == 0) { + /* Walk children for nested block_mapping */ + uint32_t vnc = ts_node_named_child_count(val); + for (uint32_t vi = 0; vi < vnc; vi++) { + TSNode vc = ts_node_named_child(val, vi); + const char *vctype = ts_node_type(vc); + if (strcmp(vctype, "block_mapping") == 0 || + strcmp(vctype, "block_mapping_pair") == 0) { + walk_yaml_mapping(ctx, vc, path); + } + } + continue; + } + + /* Leaf value: extract and classify */ + char *val_text = cbm_node_text(ctx->arena, val, ctx->source); + if (!val_text || !val_text[0]) { + continue; + } + + /* Strip quotes */ + int vlen = (int)strlen(val_text); + const char *content = val_text; + if (vlen >= 2 && (val_text[0] == '"' || val_text[0] == '\'')) { + content = val_text + 1; + vlen -= 2; + if (vlen <= 0) { + continue; + } + } + + int kind_val = cbm_classify_string(content, vlen); + if (kind_val < 0) { + continue; + } + + char *stored = cbm_arena_strndup(ctx->arena, content, (size_t)vlen); + if (!stored) { + continue; + } + + CBMStringRef ref = { + .value = stored, + .enclosing_func_qn = ctx->module_qn, + .key_path = path, + .kind = (CBMStringRefKind)kind_val, + }; + cbm_stringref_push(&ctx->result->string_refs, ctx->arena, ref); + } +} + +/* Handle YAML files: walk top-level block_mapping recursively */ +static void handle_yaml_nested(CBMExtractCtx *ctx, TSNode node) { + if (ctx->language != CBM_LANG_YAML) { + return; + } + const char *kind = ts_node_type(node); + if (strcmp(kind, "block_mapping") != 0) { + return; + } + /* Only process root-level block_mapping (depth 0 or 1) */ + TSNode parent = ts_node_parent(node); + if (ts_node_is_null(parent)) { + walk_yaml_mapping(ctx, node, NULL); + } else { + const char *pk = ts_node_type(parent); + if (strcmp(pk, "stream") == 0 || strcmp(pk, "document") == 0 || + strcmp(pk, "block_node") == 0) { + walk_yaml_mapping(ctx, node, NULL); + } + } +} + // --- Main unified cursor walk --- void cbm_extract_unified(CBMExtractCtx *ctx) { @@ -281,6 +391,7 @@ void cbm_extract_unified(CBMExtractCtx *ctx) { handle_env_accesses(ctx, node, spec, &state); handle_type_assigns(ctx, node, spec, &state); handle_string_refs(ctx, node, &state); + handle_yaml_nested(ctx, node); // 4. Push scope markers for boundary nodes if (spec->function_node_types && cbm_kind_in_set(node, spec->function_node_types)) { diff --git a/src/cli/cli.c b/src/cli/cli.c index 05c9a560..fb965d9b 100644 --- a/src/cli/cli.c +++ b/src/cli/cli.c @@ -346,7 +346,7 @@ static const char skill_tracing_content[] = "description: Call chain and dependency expert. ALWAYS invoke this skill when the user " "asks who calls a function, what a function calls, needs impact analysis, or traces " "dependencies. Do not grep for function names directly — use codebase-memory-mcp " - "trace_call_path first.\n" + "trace_path first.\n" "---\n" "\n" "# Call Tracing & Impact Analysis\n" @@ -355,7 +355,7 @@ static const char skill_tracing_content[] = "\n" "## Workflow\n" "1. `search_graph(name_pattern=\".*FuncName.*\")` — find exact function name\n" - "2. `trace_call_path(function_name=\"FuncName\", direction=\"both\")` — trace callers + " + "2. `trace_path(function_name=\"FuncName\", direction=\"both\")` — trace callers + " "callees\n" "3. `detect_changes` — find what changed and assess risk_labels\n" "\n" @@ -402,7 +402,7 @@ static const char skill_reference_content[] = "- `search_graph` — find nodes by pattern\n" "- `search_code` — text search in source\n" "- `query_graph` — Cypher query language\n" - "- `trace_call_path` — call chain traversal\n" + "- `trace_path` — call chain traversal\n" "- `get_code_snippet` — read function source\n" "- `get_graph_schema` — node/edge type catalog\n" "- `get_architecture` — high-level summary\n" @@ -429,7 +429,7 @@ static const char codex_instructions_content[] = "Use the MCP tools to explore and understand the code:\n" "\n" "- `search_graph` — find functions, classes, routes by pattern\n" - "- `trace_call_path` — trace who calls a function or what it calls\n" + "- `trace_path` — trace who calls a function or what it calls\n" "- `get_code_snippet` — read function source code\n" "- `query_graph` — run Cypher queries for complex patterns\n" "- `get_architecture` — high-level project summary\n" @@ -963,7 +963,7 @@ static const char agent_instructions_content[] = "\n" "## Priority Order\n" "1. `search_graph` — find functions, classes, routes, variables by pattern\n" - "2. `trace_call_path` — trace who calls a function or what it calls\n" + "2. `trace_path` — trace who calls a function or what it calls\n" "3. `get_code_snippet` — read specific function/class source code\n" "4. `query_graph` — run Cypher queries for complex patterns\n" "5. `get_architecture` — high-level project summary\n" @@ -975,7 +975,7 @@ static const char agent_instructions_content[] = "\n" "## Examples\n" "- Find a handler: `search_graph(name_pattern=\".*OrderHandler.*\")`\n" - "- Who calls it: `trace_call_path(function_name=\"OrderHandler\", direction=\"inbound\")`\n" + "- Who calls it: `trace_path(function_name=\"OrderHandler\", direction=\"inbound\")`\n" "- Read source: `get_code_snippet(qualified_name=\"pkg/orders.OrderHandler\")`\n"; const char *cbm_get_agent_instructions(void) { @@ -1558,7 +1558,7 @@ static void cbm_install_hook_gate_script(const char *home) { "fi\n" "touch \"$GATE\"\n" "echo 'BLOCKED: For code discovery, use codebase-memory-mcp tools first: " - "search_graph(name_pattern) to find functions/classes, trace_call_path() for " + "search_graph(name_pattern) to find functions/classes, trace_path() for " "call chains, get_code_snippet(qualified_name) to read source. If the graph " "is not indexed yet, call index_repository first. Fall back to Grep/Glob/Read " "only for text content search. If you need Grep, retry.' >&2\n" @@ -1574,8 +1574,8 @@ static void cbm_install_hook_gate_script(const char *home) { } #define GEMINI_HOOK_MATCHER "google_search|read_file|grep_search" -#define GEMINI_HOOK_COMMAND \ - "echo 'Reminder: prefer codebase-memory-mcp search_graph/trace_call_path/" \ +#define GEMINI_HOOK_COMMAND \ + "echo 'Reminder: prefer codebase-memory-mcp search_graph/trace_path/" \ "get_code_snippet over grep/file search for code discovery.' >&2" int cbm_upsert_gemini_hooks(const char *settings_path) { diff --git a/src/main.c b/src/main.c index 70eadcdc..800f9ab7 100644 --- a/src/main.c +++ b/src/main.c @@ -148,7 +148,7 @@ static void print_help(void) { printf(" --port=N Set UI port (default 9749, persisted)\n"); printf("\nSupported agents (auto-detected):\n"); printf(" Claude Code, Codex CLI, Gemini CLI, Zed, OpenCode, Antigravity, Aider, KiloCode\n"); - printf("\nTools: index_repository, search_graph, query_graph, trace_call_path,\n"); + printf("\nTools: index_repository, search_graph, query_graph, trace_path,\n"); printf(" get_code_snippet, get_graph_schema, get_architecture, search_code,\n"); printf(" list_projects, delete_project, index_status, detect_changes,\n"); printf(" manage_adr, ingest_traces\n"); diff --git a/src/mcp/mcp.c b/src/mcp/mcp.c index e1c7a5c1..f292da29 100644 --- a/src/mcp/mcp.c +++ b/src/mcp/mcp.c @@ -253,14 +253,19 @@ static const tool_def_t TOOLS[] = { "\"Optional row limit. Default: unlimited (100k " "ceiling)\"}},\"required\":[\"query\",\"project\"]}"}, - {"trace_call_path", - "Trace function call paths — who calls a function and what it calls. Use INSTEAD OF grep when " - "finding callers, dependencies, or impact analysis.", + {"trace_path", + "Trace paths through the code graph. Modes: calls (callers/callees), data_flow (value " + "propagation with args at each hop), cross_service (through HTTP/async Route nodes). " + "Use INSTEAD OF grep for callers, dependencies, impact analysis, or data flow tracing.", "{\"type\":\"object\",\"properties\":{\"function_name\":{\"type\":\"string\"},\"project\":{" "\"type\":\"string\"},\"direction\":{\"type\":\"string\",\"enum\":[\"inbound\",\"outbound\"," - "\"both\"],\"default\":\"both\"},\"depth\":{\"type\":\"integer\",\"default\":3},\"edge_" - "types\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"function_" - "name\",\"project\"]}"}, + "\"both\"],\"default\":\"both\"},\"depth\":{\"type\":\"integer\",\"default\":3},\"mode\":{" + "\"type\":\"string\",\"enum\":[\"calls\",\"data_flow\",\"cross_service\"],\"default\":" + "\"calls\",\"description\":\"calls: follow CALLS edges. data_flow: follow CALLS+DATA_FLOWS " + "with arg expressions. cross_service: follow HTTP_CALLS+ASYNC_CALLS+DATA_FLOWS through " + "Routes.\"},\"parameter_name\":{\"type\":\"string\",\"description\":\"For data_flow mode: " + "scope trace to a specific parameter name\"},\"edge_types\":{\"type\":\"array\",\"items\":{" + "\"type\":\"string\"}}},\"required\":[\"function_name\",\"project\"]}"}, {"get_code_snippet", "Read source code for a function/class/symbol. IMPORTANT: First call search_graph to find the " @@ -1223,11 +1228,15 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) { char *project = cbm_mcp_get_string_arg(args, "project"); cbm_store_t *store = resolve_store(srv, project); char *direction = cbm_mcp_get_string_arg(args, "direction"); + char *mode = cbm_mcp_get_string_arg(args, "mode"); + char *param_name = cbm_mcp_get_string_arg(args, "parameter_name"); int depth = cbm_mcp_get_int_arg(args, "depth", 3); if (!func_name) { free(project); free(direction); + free(mode); + free(param_name); return cbm_mcp_text_result("function_name is required", true); } if (!store) { @@ -1237,6 +1246,8 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) { free(func_name); free(project); free(direction); + free(mode); + free(param_name); return _res; } @@ -1245,6 +1256,8 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) { free(func_name); free(project); free(direction); + free(mode); + free(param_name); return not_indexed; } @@ -1261,6 +1274,8 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) { free(func_name); free(project); free(direction); + free(mode); + free(param_name); cbm_store_free_nodes(nodes, 0); return cbm_mcp_text_result("{\"error\":\"function not found\"}", true); } @@ -1271,9 +1286,55 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) { yyjson_mut_obj_add_str(doc, root, "function", func_name); yyjson_mut_obj_add_str(doc, root, "direction", direction); + if (mode) { + yyjson_mut_obj_add_str(doc, root, "mode", mode); + } - const char *edge_types[] = {"CALLS"}; - int edge_type_count = 1; + /* Edge types: explicit > mode-based > default */ + static const char *mode_calls[] = {"CALLS"}; + static const char *mode_data_flow[] = {"CALLS", "DATA_FLOWS"}; + static const char *mode_cross_svc[] = {"HTTP_CALLS", "ASYNC_CALLS", "DATA_FLOWS", "CALLS"}; + + const char *edge_types[16]; + int edge_type_count = 0; + + /* Try parsing explicit edge_types array from args */ + yyjson_doc *et_doc = yyjson_read(args, strlen(args), 0); + if (et_doc) { + yyjson_val *et_arr = yyjson_obj_get(yyjson_doc_get_root(et_doc), "edge_types"); + if (et_arr && yyjson_is_arr(et_arr)) { + size_t idx2; + size_t max2; + yyjson_val *val2; + yyjson_arr_foreach(et_arr, idx2, max2, val2) { + if (yyjson_is_str(val2) && edge_type_count < 16) { + edge_types[edge_type_count++] = yyjson_get_str(val2); + } + } + } + } + + yyjson_doc *et_doc_keep = et_doc; + if (edge_type_count == 0) { + /* Select defaults by mode */ + const char **defaults = mode_calls; + int n_defaults = 1; + if (mode && strcmp(mode, "data_flow") == 0) { + defaults = mode_data_flow; + n_defaults = 2; + } else if (mode && strcmp(mode, "cross_service") == 0) { + defaults = mode_cross_svc; + n_defaults = 4; + } + for (int i = 0; i < n_defaults; i++) { + edge_types[i] = defaults[i]; + } + edge_type_count = n_defaults; + if (et_doc_keep) { + yyjson_doc_free(et_doc_keep); + et_doc_keep = NULL; + } + } /* Run BFS for each requested direction. * IMPORTANT: yyjson_mut_obj_add_str borrows pointers — we must keep @@ -1338,6 +1399,11 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) { free(func_name); free(project); free(direction); + free(mode); + free(param_name); + if (et_doc_keep) { + yyjson_doc_free(et_doc_keep); + } char *result = cbm_mcp_text_result(json, false); free(json); @@ -2698,7 +2764,7 @@ char *cbm_mcp_handle_tool(cbm_mcp_server_t *srv, const char *tool_name, const ch if (strcmp(tool_name, "delete_project") == 0) { return handle_delete_project(srv, args_json); } - if (strcmp(tool_name, "trace_call_path") == 0) { + if (strcmp(tool_name, "trace_path") == 0 || strcmp(tool_name, "trace_call_path") == 0) { return handle_trace_call_path(srv, args_json); } if (strcmp(tool_name, "get_architecture") == 0) { diff --git a/src/pipeline/pass_definitions.c b/src/pipeline/pass_definitions.c index 61013369..3e0b9835 100644 --- a/src/pipeline/pass_definitions.c +++ b/src/pipeline/pass_definitions.c @@ -182,7 +182,10 @@ static void build_def_props(char *buf, size_t bufsize, const CBMDefinition *def) size_t pos = (size_t)n; append_json_string(buf, bufsize, &pos, "docstring", def->docstring); append_json_string(buf, bufsize, &pos, "signature", def->signature); + append_json_string(buf, bufsize, &pos, "return_type", def->return_type); + append_json_string(buf, bufsize, &pos, "parent_class", def->parent_class); append_json_str_array(buf, bufsize, &pos, "decorators", def->decorators); + append_json_str_array(buf, bufsize, &pos, "base_classes", def->base_classes); append_json_str_array(buf, bufsize, &pos, "param_names", def->param_names); append_json_str_array(buf, bufsize, &pos, "param_types", def->param_types); diff --git a/src/pipeline/pass_parallel.c b/src/pipeline/pass_parallel.c index 115e074a..d9fd961c 100644 --- a/src/pipeline/pass_parallel.c +++ b/src/pipeline/pass_parallel.c @@ -193,7 +193,10 @@ static void build_def_props(char *buf, size_t bufsize, const CBMDefinition *def) size_t pos = (size_t)n; append_json_string(buf, bufsize, &pos, "docstring", def->docstring); append_json_string(buf, bufsize, &pos, "signature", def->signature); + append_json_string(buf, bufsize, &pos, "return_type", def->return_type); + append_json_string(buf, bufsize, &pos, "parent_class", def->parent_class); append_json_str_array(buf, bufsize, &pos, "decorators", def->decorators); + append_json_str_array(buf, bufsize, &pos, "base_classes", def->base_classes); append_json_str_array(buf, bufsize, &pos, "param_names", def->param_names); append_json_str_array(buf, bufsize, &pos, "param_types", def->param_types); if (pos < bufsize - 1) { diff --git a/src/pipeline/pass_route_nodes.c b/src/pipeline/pass_route_nodes.c index aec7d2eb..ed56c0ad 100644 --- a/src/pipeline/pass_route_nodes.c +++ b/src/pipeline/pass_route_nodes.c @@ -242,6 +242,80 @@ static void match_infra_routes(cbm_gbuf_t *gb) { } } +/* Phase 3: Create DATA_FLOWS edges by linking callers through Route to handlers. + * For each HTTP_CALLS/ASYNC_CALLS edge (caller → Route), find the HANDLES edge + * (handler → Route) and create DATA_FLOWS (caller → handler) with route context. */ +static void create_data_flows(cbm_gbuf_t *gb) { + const cbm_gbuf_node_t **routes = NULL; + int route_count = 0; + if (cbm_gbuf_find_by_label(gb, "Route", &routes, &route_count) != 0 || route_count == 0) { + return; + } + + int flows = 0; + + /* For each Route node, find callers (HTTP_CALLS/ASYNC_CALLS → Route) + * and handlers (HANDLES → Route), then create DATA_FLOWS links. */ + for (int ri = 0; ri < route_count; ri++) { + const cbm_gbuf_node_t *route = routes[ri]; + + /* Find HTTP_CALLS → Route */ + const cbm_gbuf_edge_t **http_edges = NULL; + int http_count = 0; + cbm_gbuf_find_edges_by_target_type(gb, route->id, "HTTP_CALLS", &http_edges, &http_count); + + /* Find ASYNC_CALLS → Route */ + const cbm_gbuf_edge_t **async_edges = NULL; + int async_count = 0; + cbm_gbuf_find_edges_by_target_type(gb, route->id, "ASYNC_CALLS", &async_edges, + &async_count); + + /* Find HANDLES → Route */ + const cbm_gbuf_edge_t **handles_edges = NULL; + int handles_count = 0; + cbm_gbuf_find_edges_by_target_type(gb, route->id, "HANDLES", &handles_edges, + &handles_count); + + /* Collect caller IDs */ + int64_t callers[64]; + int n_callers = 0; + for (int ei = 0; ei < http_count && n_callers < 64; ei++) { + callers[n_callers++] = http_edges[ei]->source_id; + } + for (int ei = 0; ei < async_count && n_callers < 64; ei++) { + callers[n_callers++] = async_edges[ei]->source_id; + } + + /* Collect handler IDs */ + int64_t handlers[16]; + int n_handlers = 0; + for (int ei = 0; ei < handles_count && n_handlers < 16; ei++) { + handlers[n_handlers++] = handles_edges[ei]->source_id; + } + + /* Create DATA_FLOWS: each caller → each handler through this Route */ + for (int ci = 0; ci < n_callers; ci++) { + for (int hi = 0; hi < n_handlers; hi++) { + if (callers[ci] == handlers[hi]) { + continue; /* skip self-links */ + } + char props[512]; + snprintf(props, sizeof(props), "{\"via_route\":\"%s\",\"route_qn\":\"%s\"}", + route->name ? route->name : "", + route->qualified_name ? route->qualified_name : ""); + cbm_gbuf_insert_edge(gb, callers[ci], handlers[hi], "DATA_FLOWS", props); + flows++; + } + } + } + + if (flows > 0) { + char buf[16]; + snprintf(buf, sizeof(buf), "%d", flows); + cbm_log_info("pass.data_flows", "created", buf); + } +} + void cbm_pipeline_create_route_nodes(cbm_gbuf_t *gb) { if (!gb) { return; @@ -258,4 +332,7 @@ void cbm_pipeline_create_route_nodes(cbm_gbuf_t *gb) { /* Phase 2: match infra Routes to handler Routes by URL path */ match_infra_routes(gb); + + /* Phase 3: create DATA_FLOWS edges through Routes */ + create_data_flows(gb); } diff --git a/src/pipeline/pipeline.c b/src/pipeline/pipeline.c index 57eb3f0d..c88bcb05 100644 --- a/src/pipeline/pipeline.c +++ b/src/pipeline/pipeline.c @@ -344,8 +344,14 @@ static void cbm_pipeline_extract_infra_routes(cbm_gbuf_t *gbuf, const cbm_file_i strstr(sr->value, "://") != NULL) { char route_qn[CBM_ROUTE_QN_SIZE]; snprintf(route_qn, sizeof(route_qn), "__route__infra__%s", sr->value); - cbm_gbuf_upsert_node(gbuf, "Route", sr->value, route_qn, fp, 0, 0, - "{\"source\":\"infra\"}"); + char route_props[512]; + if (sr->key_path != NULL) { + snprintf(route_props, sizeof(route_props), + "{\"source\":\"infra\",\"key_path\":\"%s\"}", sr->key_path); + } else { + snprintf(route_props, sizeof(route_props), "{\"source\":\"infra\"}"); + } + cbm_gbuf_upsert_node(gbuf, "Route", sr->value, route_qn, fp, 0, 0, route_props); } } } diff --git a/tests/test_cli.c b/tests/test_cli.c index 0e2434cc..61cfae48 100644 --- a/tests/test_cli.c +++ b/tests/test_cli.c @@ -174,7 +174,8 @@ TEST(cli_version_get_set) { * ═══════════════════════════════════════════════════════════════════ */ TEST(cli_detect_shell_rc_zsh) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-rc-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-rc-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -197,7 +198,8 @@ TEST(cli_detect_shell_rc_zsh) { } TEST(cli_detect_shell_rc_bash) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-rc-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-rc-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -221,7 +223,8 @@ TEST(cli_detect_shell_rc_bash) { TEST(cli_detect_shell_rc_bash_with_bashrc) { /* Port of TestDetectShellRC_BashWithBashrc */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-rc-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-rc-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -248,7 +251,8 @@ TEST(cli_detect_shell_rc_bash_with_bashrc) { } TEST(cli_detect_shell_rc_fish) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-rc-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-rc-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -269,7 +273,8 @@ TEST(cli_detect_shell_rc_fish) { } TEST(cli_detect_shell_rc_default) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-rc-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-rc-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -295,7 +300,8 @@ TEST(cli_detect_shell_rc_default) { TEST(cli_find_cli_not_found) { /* Port of TestFindCLI_NotFound */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-find-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-find-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -319,7 +325,8 @@ TEST(cli_find_cli_on_path) { SKIP("PATH search differs on Windows"); #endif /* Port of TestFindCLI_FoundOnPATH */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-find-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-find-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -350,7 +357,8 @@ TEST(cli_find_cli_fallback_paths) { SKIP("shell scripts + chmod not available on Windows"); #endif /* Port of TestFindCLI_FallbackPaths */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-find-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-find-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -403,7 +411,8 @@ TEST(cli_dry_run_flags) { TEST(cli_skill_creation) { /* Port of TestInstallSkillCreation */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-skill-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-skill-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -433,7 +442,8 @@ TEST(cli_skill_creation) { TEST(cli_skill_idempotent) { /* Port of TestInstallIdempotent */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-skill-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-skill-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -462,7 +472,8 @@ TEST(cli_skill_idempotent) { TEST(cli_skill_force_overwrite) { /* Port of TestCLI_InstallForceOverwrites */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-skill-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-skill-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -481,7 +492,8 @@ TEST(cli_skill_force_overwrite) { TEST(cli_uninstall_removes_skills) { /* Port of TestUninstallRemovesSkills */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-skill-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-skill-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -507,7 +519,8 @@ TEST(cli_uninstall_removes_skills) { TEST(cli_remove_old_monolithic_skill) { /* Port of TestRemoveOldMonolithicSkill */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-skill-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-skill-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -548,7 +561,7 @@ TEST(cli_skill_files_content) { } if (strcmp(sk[i].name, "codebase-memory-tracing") == 0) { found_tracing = true; - ASSERT(strstr(sk[i].content, "trace_call_path") != NULL); + ASSERT(strstr(sk[i].content, "trace_path") != NULL); ASSERT(strstr(sk[i].content, "direction") != NULL); ASSERT(strstr(sk[i].content, "detect_changes") != NULL); } @@ -576,7 +589,7 @@ TEST(cli_codex_instructions) { const char *instr = cbm_get_codex_instructions(); ASSERT_NOT_NULL(instr); ASSERT(strstr(instr, "Codebase Knowledge Graph") != NULL); - ASSERT(strstr(instr, "trace_call_path") != NULL); + ASSERT(strstr(instr, "trace_path") != NULL); PASS(); } @@ -586,7 +599,8 @@ TEST(cli_codex_instructions) { TEST(cli_editor_mcp_install) { /* Port of TestEditorMCPInstall */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -608,7 +622,8 @@ TEST(cli_editor_mcp_install) { TEST(cli_editor_mcp_idempotent) { /* Port of TestEditorMCPInstallIdempotent */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -638,7 +653,8 @@ TEST(cli_editor_mcp_idempotent) { TEST(cli_editor_mcp_preserves_others) { /* Port of TestEditorMCPPreservesOtherServers */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -666,7 +682,8 @@ TEST(cli_editor_mcp_preserves_others) { TEST(cli_editor_mcp_uninstall) { /* Port of TestEditorMCPUninstall */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -688,7 +705,8 @@ TEST(cli_editor_mcp_uninstall) { TEST(cli_gemini_mcp_install) { /* Port of TestGeminiMCPInstall */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -714,7 +732,8 @@ TEST(cli_gemini_mcp_install) { TEST(cli_vscode_mcp_install) { /* Port of TestVSCodeMCPInstall */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -738,7 +757,8 @@ TEST(cli_vscode_mcp_install) { TEST(cli_vscode_mcp_uninstall) { /* Port of TestVSCodeMCPUninstall */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -763,7 +783,8 @@ TEST(cli_vscode_mcp_uninstall) { TEST(cli_zed_mcp_install) { /* Port of TestZedMCPInstall */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -787,7 +808,8 @@ TEST(cli_zed_mcp_install) { TEST(cli_zed_mcp_preserves_settings) { /* Port of TestZedMCPPreservesSettings */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -817,7 +839,8 @@ TEST(cli_zed_mcp_preserves_settings) { TEST(cli_zed_mcp_uninstall) { /* Port of TestZedMCPUninstall */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -838,7 +861,8 @@ TEST(cli_zed_mcp_uninstall) { TEST(cli_zed_mcp_jsonc_comments) { /* Issue #24: Zed settings.json uses JSONC (comments + trailing commas) */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-mcp-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -849,14 +873,13 @@ TEST(cli_zed_mcp_jsonc_comments) { test_mkdirp(dir); /* JSONC with comments and trailing commas — must not fail */ - write_test_file(configpath, - "// Zed settings\n" - "{\n" - " \"theme\": \"One Dark\",\n" - " /* multi-line\n" - " comment */\n" - " \"vim_mode\": true,\n" /* trailing comma */ - "}\n"); + write_test_file(configpath, "// Zed settings\n" + "{\n" + " \"theme\": \"One Dark\",\n" + " /* multi-line\n" + " comment */\n" + " \"vim_mode\": true,\n" /* trailing comma */ + "}\n"); int rc = cbm_install_zed_mcp("/usr/local/bin/codebase-memory-mcp", configpath); ASSERT_EQ(rc, 0); @@ -880,7 +903,8 @@ TEST(cli_zed_mcp_jsonc_comments) { TEST(cli_ensure_path_append) { /* Port of TestCLI_InstallPATHAppend */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-path-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-path-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -899,7 +923,8 @@ TEST(cli_ensure_path_append) { } TEST(cli_ensure_path_already_present) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-path-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-path-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -915,7 +940,8 @@ TEST(cli_ensure_path_already_present) { } TEST(cli_ensure_path_dry_run) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-path-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-path-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -940,7 +966,8 @@ TEST(cli_ensure_path_dry_run) { TEST(cli_copy_file) { /* Port of TestCopyFile */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-copy-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-copy-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -962,7 +989,8 @@ TEST(cli_copy_file) { TEST(cli_copy_file_source_not_found) { /* Port of TestCopyFile_SourceNotFound */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-copy-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-copy-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -1040,21 +1068,29 @@ static unsigned char *create_test_zip_stored(const char *filename, const unsigne int eocd_sz = 22; int total = local_hdr_sz + content_len + cd_hdr_sz + eocd_sz; unsigned char *zip = calloc(1, (size_t)total); - if (!zip) return NULL; + if (!zip) + return NULL; int pos = 0; /* Local file header */ - zip[pos] = 0x50; zip[pos+1] = 0x4B; zip[pos+2] = 0x03; zip[pos+3] = 0x04; /* signature */ - zip[pos+4] = 20; zip[pos+5] = 0; /* version needed = 2.0 */ - zip[pos+8] = 0; zip[pos+9] = 0; /* compression = stored */ - zip[pos+18] = (unsigned char)(content_len & 0xFF); - zip[pos+19] = (unsigned char)((content_len >> 8) & 0xFF); - zip[pos+20] = (unsigned char)((content_len >> 16) & 0xFF); - zip[pos+21] = (unsigned char)((content_len >> 24) & 0xFF); - zip[pos+22] = zip[pos+18]; zip[pos+23] = zip[pos+19]; - zip[pos+24] = zip[pos+20]; zip[pos+25] = zip[pos+21]; - zip[pos+26] = (unsigned char)(name_len & 0xFF); - zip[pos+27] = (unsigned char)((name_len >> 8) & 0xFF); + zip[pos] = 0x50; + zip[pos + 1] = 0x4B; + zip[pos + 2] = 0x03; + zip[pos + 3] = 0x04; /* signature */ + zip[pos + 4] = 20; + zip[pos + 5] = 0; /* version needed = 2.0 */ + zip[pos + 8] = 0; + zip[pos + 9] = 0; /* compression = stored */ + zip[pos + 18] = (unsigned char)(content_len & 0xFF); + zip[pos + 19] = (unsigned char)((content_len >> 8) & 0xFF); + zip[pos + 20] = (unsigned char)((content_len >> 16) & 0xFF); + zip[pos + 21] = (unsigned char)((content_len >> 24) & 0xFF); + zip[pos + 22] = zip[pos + 18]; + zip[pos + 23] = zip[pos + 19]; + zip[pos + 24] = zip[pos + 20]; + zip[pos + 25] = zip[pos + 21]; + zip[pos + 26] = (unsigned char)(name_len & 0xFF); + zip[pos + 27] = (unsigned char)((name_len >> 8) & 0xFF); memcpy(zip + pos + 30, filename, (size_t)name_len); pos += 30 + name_len; memcpy(zip + pos, content, (size_t)content_len); @@ -1062,27 +1098,36 @@ static unsigned char *create_test_zip_stored(const char *filename, const unsigne int cd_start = pos; /* Central directory header */ - zip[pos] = 0x50; zip[pos+1] = 0x4B; zip[pos+2] = 0x01; zip[pos+3] = 0x02; - zip[pos+10] = 0; zip[pos+11] = 0; /* compression = stored */ - zip[pos+20] = (unsigned char)(content_len & 0xFF); - zip[pos+21] = (unsigned char)((content_len >> 8) & 0xFF); - zip[pos+22] = (unsigned char)((content_len >> 16) & 0xFF); - zip[pos+23] = (unsigned char)((content_len >> 24) & 0xFF); - zip[pos+24] = zip[pos+20]; zip[pos+25] = zip[pos+21]; - zip[pos+26] = zip[pos+22]; zip[pos+27] = zip[pos+23]; - zip[pos+28] = (unsigned char)(name_len & 0xFF); - zip[pos+29] = (unsigned char)((name_len >> 8) & 0xFF); + zip[pos] = 0x50; + zip[pos + 1] = 0x4B; + zip[pos + 2] = 0x01; + zip[pos + 3] = 0x02; + zip[pos + 10] = 0; + zip[pos + 11] = 0; /* compression = stored */ + zip[pos + 20] = (unsigned char)(content_len & 0xFF); + zip[pos + 21] = (unsigned char)((content_len >> 8) & 0xFF); + zip[pos + 22] = (unsigned char)((content_len >> 16) & 0xFF); + zip[pos + 23] = (unsigned char)((content_len >> 24) & 0xFF); + zip[pos + 24] = zip[pos + 20]; + zip[pos + 25] = zip[pos + 21]; + zip[pos + 26] = zip[pos + 22]; + zip[pos + 27] = zip[pos + 23]; + zip[pos + 28] = (unsigned char)(name_len & 0xFF); + zip[pos + 29] = (unsigned char)((name_len >> 8) & 0xFF); pos += 46 + name_len; /* EOCD */ - zip[pos] = 0x50; zip[pos+1] = 0x4B; zip[pos+2] = 0x05; zip[pos+3] = 0x06; - zip[pos+8] = 1; /* num entries this disk */ - zip[pos+10] = 1; /* total entries */ + zip[pos] = 0x50; + zip[pos + 1] = 0x4B; + zip[pos + 2] = 0x05; + zip[pos + 3] = 0x06; + zip[pos + 8] = 1; /* num entries this disk */ + zip[pos + 10] = 1; /* total entries */ int cd_size = pos - cd_start; - zip[pos+12] = (unsigned char)(cd_size & 0xFF); - zip[pos+13] = (unsigned char)((cd_size >> 8) & 0xFF); - zip[pos+16] = (unsigned char)(cd_start & 0xFF); - zip[pos+17] = (unsigned char)((cd_start >> 8) & 0xFF); + zip[pos + 12] = (unsigned char)(cd_size & 0xFF); + zip[pos + 13] = (unsigned char)((cd_size >> 8) & 0xFF); + zip[pos + 16] = (unsigned char)(cd_start & 0xFF); + zip[pos + 17] = (unsigned char)((cd_start >> 8) & 0xFF); *out_len = total; return zip; @@ -1091,9 +1136,8 @@ static unsigned char *create_test_zip_stored(const char *filename, const unsigne TEST(cli_extract_binary_from_zip) { const char *content = "#!/bin/sh\necho test\n"; int zip_len = 0; - unsigned char *zip = - create_test_zip_stored("codebase-memory-mcp", (const unsigned char *)content, - (int)strlen(content), &zip_len); + unsigned char *zip = create_test_zip_stored( + "codebase-memory-mcp", (const unsigned char *)content, (int)strlen(content), &zip_len); ASSERT_NOT_NULL(zip); int out_len = 0; @@ -1109,9 +1153,8 @@ TEST(cli_extract_binary_from_zip) { TEST(cli_extract_binary_from_zip_not_found) { const char *content = "data"; int zip_len = 0; - unsigned char *zip = - create_test_zip_stored("other-file.txt", (const unsigned char *)content, - (int)strlen(content), &zip_len); + unsigned char *zip = create_test_zip_stored("other-file.txt", (const unsigned char *)content, + (int)strlen(content), &zip_len); ASSERT_NOT_NULL(zip); int out_len = 0; @@ -1150,7 +1193,8 @@ TEST(cli_extract_binary_from_zip_invalid) { TEST(cli_install_dry_run) { /* Port of TestCLI_InstallDryRun */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-dry-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-dry-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -1175,7 +1219,8 @@ TEST(cli_install_dry_run) { TEST(cli_uninstall_dry_run) { /* Port of TestCLI_UninstallDryRun */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-dry-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-dry-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -1205,7 +1250,8 @@ TEST(cli_uninstall_dry_run) { TEST(cli_install_and_uninstall) { /* Port of TestCLI_InstallAndUninstall */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-full-XXXXXX"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-full-XXXXXX"); if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); @@ -1341,8 +1387,10 @@ TEST(cli_yaml_has) { * ═══════════════════════════════════════════════════════════════════ */ TEST(cli_detect_agents_finds_claude) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char dir[512]; snprintf(dir, sizeof(dir), "%s/.claude", tmpdir); @@ -1356,8 +1404,10 @@ TEST(cli_detect_agents_finds_claude) { } TEST(cli_detect_agents_finds_codex) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char dir[512]; snprintf(dir, sizeof(dir), "%s/.codex", tmpdir); @@ -1371,8 +1421,10 @@ TEST(cli_detect_agents_finds_codex) { } TEST(cli_detect_agents_finds_gemini) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char dir[512]; snprintf(dir, sizeof(dir), "%s/.gemini", tmpdir); @@ -1386,8 +1438,10 @@ TEST(cli_detect_agents_finds_gemini) { } TEST(cli_detect_agents_finds_zed) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char dir[512]; #ifdef __APPLE__ @@ -1405,8 +1459,10 @@ TEST(cli_detect_agents_finds_zed) { } TEST(cli_detect_agents_finds_antigravity) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char dir[512]; snprintf(dir, sizeof(dir), "%s/.gemini/antigravity", tmpdir); @@ -1421,12 +1477,13 @@ TEST(cli_detect_agents_finds_antigravity) { } TEST(cli_detect_agents_finds_kilocode) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char dir[512]; - snprintf(dir, sizeof(dir), - "%s/.config/Code/User/globalStorage/kilocode.kilo-code", tmpdir); + snprintf(dir, sizeof(dir), "%s/.config/Code/User/globalStorage/kilocode.kilo-code", tmpdir); test_mkdirp(dir); cbm_detected_agents_t agents = cbm_detect_agents(tmpdir); @@ -1437,8 +1494,10 @@ TEST(cli_detect_agents_finds_kilocode) { } TEST(cli_detect_agents_none_found) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); /* Empty home dir → no config dirs → no directory-based agents detected. * Note: opencode/aider may still be detected via system fallback paths @@ -1460,8 +1519,10 @@ TEST(cli_detect_agents_none_found) { * ═══════════════════════════════════════════════════════════════════ */ TEST(cli_upsert_codex_mcp_fresh) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-codex-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-codex-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char configpath[512]; snprintf(configpath, sizeof(configpath), "%s/config.toml", tmpdir); @@ -1479,8 +1540,10 @@ TEST(cli_upsert_codex_mcp_fresh) { } TEST(cli_upsert_codex_mcp_existing) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-codex-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-codex-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char configpath[512]; snprintf(configpath, sizeof(configpath), "%s/config.toml", tmpdir); @@ -1502,16 +1565,17 @@ TEST(cli_upsert_codex_mcp_existing) { } TEST(cli_upsert_codex_mcp_replace) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-codex-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-codex-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char configpath[512]; snprintf(configpath, sizeof(configpath), "%s/config.toml", tmpdir); - write_test_file(configpath, - "[mcp_servers.codebase-memory-mcp]\n" - "command = \"/old/path/codebase-memory-mcp\"\n" - "\n" - "[other_setting]\nfoo = \"bar\"\n"); + write_test_file(configpath, "[mcp_servers.codebase-memory-mcp]\n" + "command = \"/old/path/codebase-memory-mcp\"\n" + "\n" + "[other_setting]\nfoo = \"bar\"\n"); int rc = cbm_upsert_codex_mcp("/new/path/codebase-memory-mcp", configpath); ASSERT_EQ(rc, 0); @@ -1534,8 +1598,10 @@ TEST(cli_upsert_codex_mcp_replace) { TEST(cli_zed_mcp_uses_args_format) { /* Verify Zed uses args:[""] NOT source:"custom" */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-zed-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-zed-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char configpath[512]; snprintf(configpath, sizeof(configpath), "%s/settings.json", tmpdir); @@ -1557,8 +1623,10 @@ TEST(cli_zed_mcp_uses_args_format) { * ═══════════════════════════════════════════════════════════════════ */ TEST(cli_upsert_opencode_mcp_fresh) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-ocode-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-ocode-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char configpath[512]; snprintf(configpath, sizeof(configpath), "%s/opencode.json", tmpdir); @@ -1574,15 +1642,18 @@ TEST(cli_upsert_opencode_mcp_fresh) { /* command must be emitted as an array, not a string */ ASSERT(strstr(data, "\"command\":[") != NULL || strstr(data, "\"command\": [") != NULL); /* type must be explicitly set to \"local\" */ - ASSERT(strstr(data, "\"type\":\"local\"") != NULL || strstr(data, "\"type\": \"local\"") != NULL); + ASSERT(strstr(data, "\"type\":\"local\"") != NULL || + strstr(data, "\"type\": \"local\"") != NULL); test_rmdir_r(tmpdir); PASS(); } TEST(cli_upsert_opencode_mcp_existing) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-ocode-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-ocode-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char configpath[512]; snprintf(configpath, sizeof(configpath), "%s/opencode.json", tmpdir); @@ -1605,8 +1676,10 @@ TEST(cli_upsert_opencode_mcp_existing) { * ═══════════════════════════════════════════════════════════════════ */ TEST(cli_upsert_antigravity_mcp_fresh) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-anti-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-anti-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char configpath[512]; snprintf(configpath, sizeof(configpath), "%s/mcp_config.json", tmpdir); @@ -1623,13 +1696,15 @@ TEST(cli_upsert_antigravity_mcp_fresh) { } TEST(cli_upsert_antigravity_mcp_replace) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-anti-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-anti-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char configpath[512]; snprintf(configpath, sizeof(configpath), "%s/mcp_config.json", tmpdir); write_test_file(configpath, - "{\"mcpServers\":{\"codebase-memory-mcp\":{\"command\":\"/old/path\"}}}"); + "{\"mcpServers\":{\"codebase-memory-mcp\":{\"command\":\"/old/path\"}}}"); int rc = cbm_upsert_antigravity_mcp("/new/path/codebase-memory-mcp", configpath); ASSERT_EQ(rc, 0); @@ -1648,8 +1723,10 @@ TEST(cli_upsert_antigravity_mcp_replace) { * ═══════════════════════════════════════════════════════════════════ */ TEST(cli_upsert_instructions_fresh) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-instr-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-instr-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char filepath[512]; snprintf(filepath, sizeof(filepath), "%s/AGENTS.md", tmpdir); @@ -1668,8 +1745,10 @@ TEST(cli_upsert_instructions_fresh) { } TEST(cli_upsert_instructions_existing) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-instr-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-instr-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char filepath[512]; snprintf(filepath, sizeof(filepath), "%s/AGENTS.md", tmpdir); @@ -1692,17 +1771,18 @@ TEST(cli_upsert_instructions_existing) { } TEST(cli_upsert_instructions_replace) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-instr-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-instr-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char filepath[512]; snprintf(filepath, sizeof(filepath), "%s/AGENTS.md", tmpdir); - write_test_file(filepath, - "# Rules\n" - "\n" - "OLD CONTENT\n" - "\n" - "# Other stuff\n"); + write_test_file(filepath, "# Rules\n" + "\n" + "OLD CONTENT\n" + "\n" + "# Other stuff\n"); int rc = cbm_upsert_instructions(filepath, "NEW CONTENT\n"); ASSERT_EQ(rc, 0); @@ -1721,8 +1801,10 @@ TEST(cli_upsert_instructions_replace) { } TEST(cli_upsert_instructions_no_duplicate) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-instr-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-instr-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char filepath[512]; snprintf(filepath, sizeof(filepath), "%s/AGENTS.md", tmpdir); @@ -1736,7 +1818,10 @@ TEST(cli_upsert_instructions_no_duplicate) { /* Only one start marker */ int count = 0; const char *p = data; - while ((p = strstr(p, "codebase-memory-mcp:start")) != NULL) { count++; p += 25; } + while ((p = strstr(p, "codebase-memory-mcp:start")) != NULL) { + count++; + p += 25; + } ASSERT_EQ(count, 1); /* Latest content */ ASSERT(strstr(data, "Content v2") != NULL); @@ -1747,17 +1832,18 @@ TEST(cli_upsert_instructions_no_duplicate) { } TEST(cli_remove_instructions) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-instr-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-instr-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char filepath[512]; snprintf(filepath, sizeof(filepath), "%s/AGENTS.md", tmpdir); - write_test_file(filepath, - "# Rules\n" - "\n" - "CMM Content\n" - "\n" - "# Other\n"); + write_test_file(filepath, "# Rules\n" + "\n" + "CMM Content\n" + "\n" + "# Other\n"); int rc = cbm_remove_instructions(filepath); ASSERT_EQ(rc, 0); @@ -1777,7 +1863,7 @@ TEST(cli_agent_instructions_content) { const char *instr = cbm_get_agent_instructions(); ASSERT_NOT_NULL(instr); ASSERT(strstr(instr, "search_graph") != NULL); - ASSERT(strstr(instr, "trace_call_path") != NULL); + ASSERT(strstr(instr, "trace_path") != NULL); ASSERT(strstr(instr, "get_code_snippet") != NULL); PASS(); } @@ -1787,8 +1873,10 @@ TEST(cli_agent_instructions_content) { * ═══════════════════════════════════════════════════════════════════ */ TEST(cli_upsert_claude_hook_fresh) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-hook-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-hook-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char settingspath[512]; snprintf(settingspath, sizeof(settingspath), "%s/settings.json", tmpdir); @@ -1807,15 +1895,17 @@ TEST(cli_upsert_claude_hook_fresh) { } TEST(cli_upsert_claude_hook_existing) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-hook-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-hook-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char settingspath[512]; snprintf(settingspath, sizeof(settingspath), "%s/settings.json", tmpdir); /* Pre-existing settings with other hooks */ write_test_file(settingspath, - "{\"hooks\":{\"PreToolUse\":[{\"matcher\":\"Bash\"," - "\"hooks\":[{\"type\":\"command\",\"command\":\"echo firewall\"}]}]}}"); + "{\"hooks\":{\"PreToolUse\":[{\"matcher\":\"Bash\"," + "\"hooks\":[{\"type\":\"command\",\"command\":\"echo firewall\"}]}]}}"); int rc = cbm_upsert_claude_hooks(settingspath); ASSERT_EQ(rc, 0); @@ -1833,15 +1923,17 @@ TEST(cli_upsert_claude_hook_existing) { } TEST(cli_upsert_claude_hook_replace) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-hook-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-hook-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char settingspath[512]; snprintf(settingspath, sizeof(settingspath), "%s/settings.json", tmpdir); /* Pre-existing CMM hook with old message */ write_test_file(settingspath, - "{\"hooks\":{\"PreToolUse\":[{\"matcher\":\"Grep|Glob|Read\"," - "\"hooks\":[{\"type\":\"command\",\"command\":\"echo old-cmm-message\"}]}]}}"); + "{\"hooks\":{\"PreToolUse\":[{\"matcher\":\"Grep|Glob|Read\"," + "\"hooks\":[{\"type\":\"command\",\"command\":\"echo old-cmm-message\"}]}]}}"); int rc = cbm_upsert_claude_hooks(settingspath); ASSERT_EQ(rc, 0); @@ -1857,15 +1949,17 @@ TEST(cli_upsert_claude_hook_replace) { } TEST(cli_upsert_claude_hook_preserves_others) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-hook-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-hook-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char settingspath[512]; snprintf(settingspath, sizeof(settingspath), "%s/settings.json", tmpdir); write_test_file(settingspath, - "{\"apiKey\":\"sk-123\"," - "\"hooks\":{\"PreToolUse\":[{\"matcher\":\"Bash\"," - "\"hooks\":[{\"type\":\"command\",\"command\":\"echo guard\"}]}]}}"); + "{\"apiKey\":\"sk-123\"," + "\"hooks\":{\"PreToolUse\":[{\"matcher\":\"Bash\"," + "\"hooks\":[{\"type\":\"command\",\"command\":\"echo guard\"}]}]}}"); cbm_upsert_claude_hooks(settingspath); @@ -1883,8 +1977,10 @@ TEST(cli_upsert_claude_hook_preserves_others) { } TEST(cli_remove_claude_hooks) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-hook-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-hook-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char settingspath[512]; snprintf(settingspath, sizeof(settingspath), "%s/settings.json", tmpdir); @@ -1907,8 +2003,10 @@ TEST(cli_remove_claude_hooks) { * ═══════════════════════════════════════════════════════════════════ */ TEST(cli_upsert_gemini_hook_fresh) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-ghook-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-ghook-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char settingspath[512]; snprintf(settingspath, sizeof(settingspath), "%s/settings.json", tmpdir); @@ -1926,14 +2024,16 @@ TEST(cli_upsert_gemini_hook_fresh) { } TEST(cli_upsert_gemini_hook_existing) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-ghook-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-ghook-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char settingspath[512]; snprintf(settingspath, sizeof(settingspath), "%s/settings.json", tmpdir); write_test_file(settingspath, - "{\"hooks\":{\"BeforeTool\":[{\"matcher\":\"shell\"," - "\"hooks\":[{\"type\":\"command\",\"command\":\"echo guard\"}]}]}}"); + "{\"hooks\":{\"BeforeTool\":[{\"matcher\":\"shell\"," + "\"hooks\":[{\"type\":\"command\",\"command\":\"echo guard\"}]}]}}"); int rc = cbm_upsert_gemini_hooks(settingspath); ASSERT_EQ(rc, 0); @@ -1950,12 +2050,15 @@ TEST(cli_upsert_gemini_hook_existing) { } TEST(cli_upsert_gemini_hook_replace) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-ghook-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-ghook-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char settingspath[512]; snprintf(settingspath, sizeof(settingspath), "%s/settings.json", tmpdir); - write_test_file(settingspath, + write_test_file( + settingspath, "{\"hooks\":{\"BeforeTool\":[{\"matcher\":\"google_search|read_file|grep_search\"," "\"hooks\":[{\"type\":\"command\",\"command\":\"echo old-cmm\"}]}]}}"); @@ -1972,8 +2075,10 @@ TEST(cli_upsert_gemini_hook_replace) { } TEST(cli_remove_gemini_hooks) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-ghook-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-ghook-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); char settingspath[512]; snprintf(settingspath, sizeof(settingspath), "%s/settings.json", tmpdir); @@ -2009,8 +2114,10 @@ TEST(cli_skill_descriptions_directive) { * ═══════════════════════════════════════════════════════════════════ */ TEST(cli_config_open_close) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-cfg-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-cfg-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); cbm_config_t *cfg = cbm_config_open(tmpdir); ASSERT_NOT_NULL(cfg); @@ -2027,8 +2134,10 @@ TEST(cli_config_open_close) { } TEST(cli_config_get_set) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-cfg-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-cfg-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); cbm_config_t *cfg = cbm_config_open(tmpdir); ASSERT_NOT_NULL(cfg); @@ -2050,8 +2159,10 @@ TEST(cli_config_get_set) { } TEST(cli_config_get_bool) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-cfg-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-cfg-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); cbm_config_t *cfg = cbm_config_open(tmpdir); ASSERT_NOT_NULL(cfg); @@ -2080,8 +2191,10 @@ TEST(cli_config_get_bool) { } TEST(cli_config_get_int) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-cfg-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-cfg-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); cbm_config_t *cfg = cbm_config_open(tmpdir); ASSERT_NOT_NULL(cfg); @@ -2101,8 +2214,10 @@ TEST(cli_config_get_int) { } TEST(cli_config_delete) { - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-cfg-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-cfg-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); cbm_config_t *cfg = cbm_config_open(tmpdir); ASSERT_NOT_NULL(cfg); @@ -2120,8 +2235,10 @@ TEST(cli_config_delete) { TEST(cli_config_persists) { /* Values survive close + reopen */ - char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-cfg-XXXXXX"); - if (!cbm_mkdtemp(tmpdir)) SKIP("cbm_mkdtemp failed"); + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-cfg-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + SKIP("cbm_mkdtemp failed"); cbm_config_t *cfg = cbm_config_open(tmpdir); ASSERT_NOT_NULL(cfg); diff --git a/tests/test_incremental.c b/tests/test_incremental.c index 33dea6ce..477479d5 100644 --- a/tests/test_incremental.c +++ b/tests/test_incremental.c @@ -111,14 +111,17 @@ static char *call_tool(const char *tool, const char *args_fmt, ...) { /* Parse integer from JSON response (handles nested MCP envelope) */ static int count_in_response(const char *resp, const char *key) { - if (!resp) return -1; + if (!resp) + return -1; char pattern[64]; snprintf(pattern, sizeof(pattern), "\"%s\":", key); const char *p = strstr(resp, pattern); - if (p) return atoi(p + strlen(pattern)); + if (p) + return atoi(p + strlen(pattern)); snprintf(pattern, sizeof(pattern), "\\\"%s\\\":", key); p = strstr(resp, pattern); - if (p) return atoi(p + strlen(pattern)); + if (p) + return atoi(p + strlen(pattern)); return -1; } @@ -130,7 +133,8 @@ static cbm_store_t *open_store(void) { static int get_node_count(void) { cbm_store_t *s = open_store(); - if (!s) return -1; + if (!s) + return -1; int c = cbm_store_count_nodes(s, g_project); cbm_store_close(s); return c; @@ -138,7 +142,8 @@ static int get_node_count(void) { static int get_edge_count(void) { cbm_store_t *s = open_store(); - if (!s) return -1; + if (!s) + return -1; int c = cbm_store_count_edges(s, g_project); cbm_store_close(s); return c; @@ -146,7 +151,8 @@ static int get_edge_count(void) { static int get_edge_count_by_type(const char *type) { cbm_store_t *s = open_store(); - if (!s) return -1; + if (!s) + return -1; int c = cbm_store_count_edges_by_type(s, g_project, type); cbm_store_close(s); return c; @@ -162,8 +168,8 @@ static int has_function(const char *name_pattern) { } static int count_by_label(const char *label) { - char *resp = call_tool("search_graph", - "{\"project\":\"%s\",\"label\":\"%s\"}", g_project, label); + char *resp = + call_tool("search_graph", "{\"project\":\"%s\",\"label\":\"%s\"}", g_project, label); int total = count_in_response(resp, "total"); free(resp); return total; @@ -194,7 +200,8 @@ static int incremental_setup(void) { return -1; const char *home = getenv("HOME"); - if (!home) home = "/tmp"; + if (!home) + home = "/tmp"; snprintf(g_dbpath, sizeof(g_dbpath), "%s/.cache/codebase-memory-mcp/%s.db", home, g_project); char cache_dir[512]; @@ -299,8 +306,8 @@ TEST(incr_full_edge_types) { /* CALLS should be the most common edge type */ ASSERT_GT(calls, imports); - printf(" [edges] CALLS=%d IMPORTS=%d DEFINES=%d CONTAINS_FILE=%d\n", - calls, imports, defines, contains); + printf(" [edges] CALLS=%d IMPORTS=%d DEFINES=%d CONTAINS_FILE=%d\n", calls, imports, defines, + contains); PASS(); } @@ -341,12 +348,11 @@ TEST(incr_modify_file) { snprintf(path, sizeof(path), "%s/fastapi/applications.py", g_repodir); FILE *f = fopen(path, "a"); ASSERT(f != NULL); - fprintf(f, - "\n\ndef incr_test_injected(x: int) -> int:\n" - " return x * 42\n" - "\n" - "def incr_test_helper(y: str) -> str:\n" - " return y.upper()\n"); + fprintf(f, "\n\ndef incr_test_injected(x: int) -> int:\n" + " return x * 42\n" + "\n" + "def incr_test_helper(y: str) -> str:\n" + " return y.upper()\n"); fclose(f); double ms = 0; @@ -401,8 +407,8 @@ TEST(incr_formatter_run) { int calls_diff = abs(get_edge_count_by_type("CALLS") - calls_before); ASSERT_LT(calls_diff, calls_before / 4); - printf(" [perf] reformat 50 files: %.0fms, node_diff=%d edge_diff=%d\n", - ms, node_diff, edge_diff); + printf(" [perf] reformat 50 files: %.0fms, node_diff=%d edge_diff=%d\n", ms, node_diff, + edge_diff); PASS(); } @@ -410,25 +416,24 @@ TEST(incr_formatter_run) { TEST(incr_add_file) { int nodes_before = get_node_count(); - write_file_at("fastapi/incr_test_new.py", - "\"\"\"New module.\"\"\"\n" - "from fastapi import FastAPI\n" - "\n" - "def incr_new_entry(app: FastAPI) -> None:\n" - " setup(app)\n" - "\n" - "def setup(app: FastAPI) -> None:\n" - " pass\n" - "\n" - "def incr_new_validate(data: dict) -> bool:\n" - " return bool(data)\n" - "\n" - "class IncrNewHandler:\n" - " def handle(self, req):\n" - " return incr_new_validate(req)\n" - "\n" - " def cleanup(self):\n" - " pass\n"); + write_file_at("fastapi/incr_test_new.py", "\"\"\"New module.\"\"\"\n" + "from fastapi import FastAPI\n" + "\n" + "def incr_new_entry(app: FastAPI) -> None:\n" + " setup(app)\n" + "\n" + "def setup(app: FastAPI) -> None:\n" + " pass\n" + "\n" + "def incr_new_validate(data: dict) -> bool:\n" + " return bool(data)\n" + "\n" + "class IncrNewHandler:\n" + " def handle(self, req):\n" + " return incr_new_validate(req)\n" + "\n" + " def cleanup(self):\n" + " pass\n"); char *resp = index_repo(); ASSERT(resp != NULL); @@ -462,8 +467,7 @@ TEST(incr_delete_file) { TEST(incr_simultaneous_changes) { /* Add */ - write_file_at("tests/incr_simul_add.py", - "def incr_simul_added():\n return 'added'\n"); + write_file_at("tests/incr_simul_add.py", "def incr_simul_added():\n return 'added'\n"); /* Modify */ char path[512]; @@ -506,13 +510,12 @@ TEST(incr_empty_file) { TEST(incr_syntax_error) { int nodes_before = get_node_count(); - write_file_at("fastapi/incr_broken.py", - "def broken(\n" - " # deliberately broken\n" - "class @#$% {\n" - " async def nested() -> None\n" - " yield from broken(\n" - "))))\n"); + write_file_at("fastapi/incr_broken.py", "def broken(\n" + " # deliberately broken\n" + "class @#$% {\n" + " async def nested() -> None\n" + " yield from broken(\n" + "))))\n"); char *resp = index_repo(); ASSERT(resp != NULL); @@ -563,7 +566,8 @@ TEST(incr_binary_content) { FILE *f = fopen(path, "wb"); if (f) { unsigned char bin[256]; - for (int i = 0; i < 256; i++) bin[i] = (unsigned char)i; + for (int i = 0; i < 256; i++) + bin[i] = (unsigned char)i; fwrite(bin, 1, sizeof(bin), f); fclose(f); } @@ -584,8 +588,7 @@ TEST(incr_large_generated) { content[0] = '\0'; for (int i = 0; i < 300; i++) { char line[80]; - snprintf(line, sizeof(line), - "def incr_gen_%d(x):\n return x + %d\n\n", i, i); + snprintf(line, sizeof(line), "def incr_gen_%d(x):\n return x + %d\n\n", i, i); strcat(content, line); } @@ -606,9 +609,8 @@ TEST(incr_large_generated) { TEST(incr_new_subdir) { /* File in a brand new subdirectory */ write_file_at("fastapi/newpkg/__init__.py", ""); - write_file_at("fastapi/newpkg/handler.py", - "def newpkg_handler():\n" - " return 'from_new_package'\n"); + write_file_at("fastapi/newpkg/handler.py", "def newpkg_handler():\n" + " return 'from_new_package'\n"); char *resp = index_repo(); ASSERT(resp != NULL); @@ -646,9 +648,8 @@ TEST(incr_rapid_reindex) { } TEST(incr_replace_file_content) { - write_file_at("fastapi/incr_replace.py", - "def replace_original_a():\n return 'a'\n" - "def replace_original_b():\n return 'b'\n"); + write_file_at("fastapi/incr_replace.py", "def replace_original_a():\n return 'a'\n" + "def replace_original_b():\n return 'b'\n"); char *resp = index_repo(); ASSERT(resp != NULL); @@ -658,9 +659,8 @@ TEST(incr_replace_file_content) { ASSERT(has_function("replace_original_b")); /* Replace entirely */ - write_file_at("fastapi/incr_replace.py", - "def replace_new_x():\n return 'x'\n" - "def replace_new_y():\n return replace_new_x()\n"); + write_file_at("fastapi/incr_replace.py", "def replace_new_x():\n return 'x'\n" + "def replace_new_y():\n return replace_new_x()\n"); resp = index_repo(); ASSERT(resp != NULL); @@ -741,9 +741,8 @@ TEST(incr_db_deleted_recovery) { TEST(incr_accuracy_vs_full) { /* Modify a file to create a known incremental state */ - write_file_at("fastapi/incr_accuracy.py", - "def accuracy_a():\n return 1\n" - "def accuracy_b():\n return accuracy_a() + 1\n"); + write_file_at("fastapi/incr_accuracy.py", "def accuracy_a():\n return 1\n" + "def accuracy_b():\n return accuracy_a() + 1\n"); char *resp = index_repo(); ASSERT(resp != NULL); @@ -768,8 +767,8 @@ TEST(incr_accuracy_vs_full) { ASSERT_LTE(abs(full_edges - incr_edges), 2); ASSERT_LTE(abs(full_calls - incr_calls), 2); - printf(" [accuracy] incr: %d nodes/%d edges, full: %d nodes/%d edges\n", - incr_nodes, incr_edges, full_nodes, full_edges); + printf(" [accuracy] incr: %d nodes/%d edges, full: %d nodes/%d edges\n", incr_nodes, + incr_edges, full_nodes, full_edges); delete_file_at("fastapi/incr_accuracy.py"); PASS(); @@ -781,8 +780,7 @@ TEST(incr_accuracy_vs_full) { TEST(incr_perf_single_file_fast) { /* Modifying one file should complete in <2s (not re-parse everything) */ - write_file_at("fastapi/incr_perf_probe.py", - "def perf_probe():\n return 42\n"); + write_file_at("fastapi/incr_perf_probe.py", "def perf_probe():\n return 42\n"); double ms = 0; size_t peak_mb = 0; @@ -821,7 +819,8 @@ static char *call_tool_timed(const char *tool, double *ms, const char *args_fmt, /* Check if ALL results have a specific label (validates label filter works) */ static int all_results_have_label(const char *resp, const char *label) { - if (!resp) return 0; + if (!resp) + return 0; /* Every "label":"X" in results must match. Check no other label appears. */ char pattern[64]; snprintf(pattern, sizeof(pattern), "\"label\":\"%s\"", label); @@ -833,7 +832,10 @@ static int all_results_have_label(const char *resp, const char *label) { /* Skip the key name — find the value */ p += 5; /* past "label" */ const char *val = strstr(p, ":\""); - if (!val || val > p + 10) { p++; continue; } + if (!val || val > p + 10) { + p++; + continue; + } val += 2; /* Check this label value matches */ if (strncmp(val, label, strlen(label)) != 0) { @@ -841,7 +843,8 @@ static int all_results_have_label(const char *resp, const char *label) { const char *eval = strstr(p, ":\\\""); if (eval && eval < p + 10) { eval += 3; - if (strncmp(eval, label, strlen(label)) != 0) return 0; + if (strncmp(eval, label, strlen(label)) != 0) + return 0; } else { return 0; } @@ -853,10 +856,12 @@ static int all_results_have_label(const char *resp, const char *label) { /* Check response contains a specific key (in nested JSON) */ static int resp_has_key(const char *resp, const char *key) { - if (!resp) return 0; + if (!resp) + return 0; char pattern[64]; snprintf(pattern, sizeof(pattern), "\"%s\"", key); - if (strstr(resp, pattern) != NULL) return 1; + if (strstr(resp, pattern) != NULL) + return 1; snprintf(pattern, sizeof(pattern), "\\\"%s\\\"", key); return strstr(resp, pattern) != NULL; } @@ -867,15 +872,14 @@ static int resp_lacks_key(const char *resp, const char *key) { } /* Helper: assert tool call succeeds within timeout */ -#define TOOL_OK(resp, ms) \ - do { \ - ASSERT((resp) != NULL); \ +#define TOOL_OK(resp, ms) \ + do { \ + ASSERT((resp) != NULL); \ ASSERT_LT((int)(ms), MAX_TOOL_MS); \ } while (0) /* Helper: assert response is not an error */ -#define NOT_ERROR(resp) \ - ASSERT(strstr((resp), "\"isError\":true") == NULL) +#define NOT_ERROR(resp) ASSERT(strstr((resp), "\"isError\":true") == NULL) /* ── list_projects ─────────────────────────────────────────────── */ @@ -904,8 +908,7 @@ TEST(tool_list_projects_has_current) { TEST(tool_index_status_basic) { double ms; - char *r = call_tool_timed("index_status", &ms, - "{\"project\":\"%s\"}", g_project); + char *r = call_tool_timed("index_status", &ms, "{\"project\":\"%s\"}", g_project); TOOL_OK(r, ms); ASSERT(strstr(r, "nodes") != NULL || strstr(r, "indexed") != NULL); free(r); @@ -914,8 +917,7 @@ TEST(tool_index_status_basic) { TEST(tool_index_status_nonexistent) { double ms; - char *r = call_tool_timed("index_status", &ms, - "{\"project\":\"does-not-exist-xyz\"}"); + char *r = call_tool_timed("index_status", &ms, "{\"project\":\"does-not-exist-xyz\"}"); TOOL_OK(r, ms); ASSERT(strstr(r, "not") != NULL || strstr(r, "error") != NULL); free(r); @@ -926,8 +928,7 @@ TEST(tool_index_status_nonexistent) { TEST(tool_schema_has_labels) { double ms; - char *r = call_tool_timed("get_graph_schema", &ms, - "{\"project\":\"%s\"}", g_project); + char *r = call_tool_timed("get_graph_schema", &ms, "{\"project\":\"%s\"}", g_project); TOOL_OK(r, ms); ASSERT(strstr(r, "Function") != NULL); ASSERT(strstr(r, "Method") != NULL); @@ -939,8 +940,7 @@ TEST(tool_schema_has_labels) { TEST(tool_schema_has_edge_types) { double ms; - char *r = call_tool_timed("get_graph_schema", &ms, - "{\"project\":\"%s\"}", g_project); + char *r = call_tool_timed("get_graph_schema", &ms, "{\"project\":\"%s\"}", g_project); TOOL_OK(r, ms); ASSERT(strstr(r, "CALLS") != NULL); ASSERT(strstr(r, "IMPORTS") != NULL); @@ -985,8 +985,8 @@ TEST(tool_sg_label_method) { TEST(tool_sg_label_module) { double ms; - char *r = call_tool_timed("search_graph", &ms, - "{\"project\":\"%s\",\"label\":\"Module\"}", g_project); + char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Module\"}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_GT(total, 200); @@ -996,8 +996,8 @@ TEST(tool_sg_label_module) { TEST(tool_sg_label_variable) { double ms; - char *r = call_tool_timed("search_graph", &ms, - "{\"project\":\"%s\",\"label\":\"Variable\"}", g_project); + char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Variable\"}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_GT(total, 0); @@ -1007,8 +1007,8 @@ TEST(tool_sg_label_variable) { TEST(tool_sg_label_class) { double ms; - char *r = call_tool_timed("search_graph", &ms, - "{\"project\":\"%s\",\"label\":\"Class\"}", g_project); + char *r = + call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Class\"}", g_project); TOOL_OK(r, ms); /* FastAPI has classes */ int total = count_in_response(r, "total"); @@ -1019,8 +1019,8 @@ TEST(tool_sg_label_class) { TEST(tool_sg_label_route) { double ms; - char *r = call_tool_timed("search_graph", &ms, - "{\"project\":\"%s\",\"label\":\"Route\"}", g_project); + char *r = + call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Route\"}", g_project); TOOL_OK(r, ms); /* May or may not have routes */ ASSERT(strstr(r, "total") != NULL || strstr(r, "results") != NULL); @@ -1030,8 +1030,8 @@ TEST(tool_sg_label_route) { TEST(tool_sg_label_nonexistent) { double ms; - char *r = call_tool_timed("search_graph", &ms, - "{\"project\":\"%s\",\"label\":\"Nonexistent\"}", g_project); + char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Nonexistent\"}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_EQ(total, 0); @@ -1045,7 +1045,8 @@ TEST(tool_sg_name_exact) { double ms; char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Function\"," - "\"name_pattern\":\"incr_test_injected\"}", g_project); + "\"name_pattern\":\"incr_test_injected\"}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_EQ(total, 1); @@ -1057,7 +1058,8 @@ TEST(tool_sg_name_regex) { double ms; char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Function\"," - "\"name_pattern\":\".*test.*\"}", g_project); + "\"name_pattern\":\".*test.*\"}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_GT(total, 0); @@ -1069,7 +1071,8 @@ TEST(tool_sg_name_no_match) { double ms; char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Function\"," - "\"name_pattern\":\"zzz_nonexistent_zzz\"}", g_project); + "\"name_pattern\":\"zzz_nonexistent_zzz\"}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_EQ(total, 0); @@ -1083,7 +1086,8 @@ TEST(tool_sg_min_degree) { double ms; char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Function\"," - "\"min_degree\":10}", g_project); + "\"min_degree\":10}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_GT(total, 0); @@ -1095,7 +1099,8 @@ TEST(tool_sg_max_degree_zero) { double ms; char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Function\"," - "\"max_degree\":0}", g_project); + "\"max_degree\":0}", + g_project); TOOL_OK(r, ms); /* Isolated functions (no edges) */ int total = count_in_response(r, "total"); @@ -1108,7 +1113,8 @@ TEST(tool_sg_degree_range) { double ms; char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Function\"," - "\"min_degree\":2,\"max_degree\":5}", g_project); + "\"min_degree\":2,\"max_degree\":5}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_GT(total, 0); @@ -1122,7 +1128,8 @@ TEST(tool_sg_limit) { double ms; char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Function\"," - "\"limit\":3}", g_project); + "\"limit\":3}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); /* total should show real count (much more than 3) */ @@ -1137,7 +1144,8 @@ TEST(tool_sg_offset) { double ms; char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Function\"," - "\"limit\":5,\"offset\":10}", g_project); + "\"limit\":5,\"offset\":10}", + g_project); TOOL_OK(r, ms); ASSERT(strstr(r, "results") != NULL); free(r); @@ -1151,7 +1159,8 @@ TEST(tool_sg_file_pattern) { /* file_pattern is glob (converted to SQL LIKE), not regex */ char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Function\"," - "\"file_pattern\":\"*fastapi*\",\"limit\":5}", g_project); + "\"file_pattern\":\"*fastapi*\",\"limit\":5}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_GT(total, 0); @@ -1166,7 +1175,8 @@ TEST(tool_sg_include_connected) { char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Function\"," "\"name_pattern\":\"incr_test_injected\"," - "\"include_connected\":true}", g_project); + "\"include_connected\":true}", + g_project); TOOL_OK(r, ms); ASSERT(strstr(r, "connected") != NULL || strstr(r, "results") != NULL); free(r); @@ -1179,7 +1189,8 @@ TEST(tool_sg_relationship) { double ms; char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Function\"," - "\"relationship\":\"CALLS\",\"min_degree\":1}", g_project); + "\"relationship\":\"CALLS\",\"min_degree\":1}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_GT(total, 0); @@ -1193,7 +1204,8 @@ TEST(tool_sg_qn_pattern) { double ms; char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\"," - "\"qn_pattern\":\".*fastapi.*applications.*\"}", g_project); + "\"qn_pattern\":\".*fastapi.*applications.*\"}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_GT(total, 0); @@ -1208,7 +1220,8 @@ TEST(tool_sg_combined_filters) { char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Function\"," "\"name_pattern\":\".*__init__.*\"," - "\"min_degree\":1,\"limit\":5}", g_project); + "\"min_degree\":1,\"limit\":5}", + g_project); TOOL_OK(r, ms); ASSERT(strstr(r, "results") != NULL); free(r); @@ -1219,8 +1232,7 @@ TEST(tool_sg_combined_filters) { TEST(tool_sg_project_only) { double ms; - char *r = call_tool_timed("search_graph", &ms, - "{\"project\":\"%s\"}", g_project); + char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\"}", g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_GT(total, 1000); @@ -1247,7 +1259,8 @@ TEST(tool_qg_match_nodes) { double ms; char *r = call_tool_timed("query_graph", &ms, "{\"project\":\"%s\"," - "\"query\":\"MATCH (n:Function) RETURN n.name LIMIT 10\"}", g_project); + "\"query\":\"MATCH (n:Function) RETURN n.name LIMIT 10\"}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_GT(total, 0); @@ -1258,10 +1271,11 @@ TEST(tool_qg_match_nodes) { TEST(tool_qg_match_edges) { double ms; - char *r = call_tool_timed("query_graph", &ms, - "{\"project\":\"%s\"," - "\"query\":\"MATCH (a)-[r:CALLS]->(b) RETURN a.name, b.name LIMIT 10\"}", - g_project); + char *r = + call_tool_timed("query_graph", &ms, + "{\"project\":\"%s\"," + "\"query\":\"MATCH (a)-[r:CALLS]->(b) RETURN a.name, b.name LIMIT 10\"}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_GT(total, 0); @@ -1271,10 +1285,11 @@ TEST(tool_qg_match_edges) { TEST(tool_qg_match_imports) { double ms; - char *r = call_tool_timed("query_graph", &ms, - "{\"project\":\"%s\"," - "\"query\":\"MATCH (a)-[r:IMPORTS]->(b) RETURN a.name, b.name LIMIT 10\"}", - g_project); + char *r = + call_tool_timed("query_graph", &ms, + "{\"project\":\"%s\"," + "\"query\":\"MATCH (a)-[r:IMPORTS]->(b) RETURN a.name, b.name LIMIT 10\"}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_GT(total, 0); @@ -1284,10 +1299,11 @@ TEST(tool_qg_match_imports) { TEST(tool_qg_match_defines) { double ms; - char *r = call_tool_timed("query_graph", &ms, - "{\"project\":\"%s\"," - "\"query\":\"MATCH (a)-[r:DEFINES]->(b) RETURN a.name, b.name LIMIT 10\"}", - g_project); + char *r = + call_tool_timed("query_graph", &ms, + "{\"project\":\"%s\"," + "\"query\":\"MATCH (a)-[r:DEFINES]->(b) RETURN a.name, b.name LIMIT 10\"}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_GT(total, 0); @@ -1297,10 +1313,11 @@ TEST(tool_qg_match_defines) { TEST(tool_qg_match_contains) { double ms; - char *r = call_tool_timed("query_graph", &ms, - "{\"project\":\"%s\"," - "\"query\":\"MATCH (a)-[r:CONTAINS_FILE]->(b) RETURN a.name, b.name LIMIT 5\"}", - g_project); + char *r = call_tool_timed( + "query_graph", &ms, + "{\"project\":\"%s\"," + "\"query\":\"MATCH (a)-[r:CONTAINS_FILE]->(b) RETURN a.name, b.name LIMIT 5\"}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_GT(total, 0); @@ -1312,10 +1329,11 @@ TEST(tool_qg_match_contains) { TEST(tool_qg_where_name) { double ms; - char *r = call_tool_timed("query_graph", &ms, - "{\"project\":\"%s\"," - "\"query\":\"MATCH (n:Function) WHERE n.name = 'incr_test_injected' RETURN n.name\"}", - g_project); + char *r = call_tool_timed( + "query_graph", &ms, + "{\"project\":\"%s\"," + "\"query\":\"MATCH (n:Function) WHERE n.name = 'incr_test_injected' RETURN n.name\"}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_EQ(total, 1); @@ -1329,7 +1347,8 @@ TEST(tool_qg_two_hop) { double ms; char *r = call_tool_timed("query_graph", &ms, "{\"project\":\"%s\"," - "\"query\":\"MATCH (a)-[:CALLS]->(b)-[:CALLS]->(c) RETURN a.name, b.name, c.name LIMIT 5\"}", + "\"query\":\"MATCH (a)-[:CALLS]->(b)-[:CALLS]->(c) RETURN a.name, " + "b.name, c.name LIMIT 5\"}", g_project); TOOL_OK(r, ms); /* May have 2-hop call chains */ @@ -1355,7 +1374,8 @@ TEST(tool_qg_max_rows) { char *r2 = call_tool_timed("query_graph", &ms, "{\"project\":\"%s\"," "\"query\":\"MATCH (n:Function) RETURN n.name\"," - "\"max_rows\":3}", g_project); + "\"max_rows\":3}", + g_project); TOOL_OK(r2, ms); int total_limited = count_in_response(r2, "total"); ASSERT_LTE(total_limited, 3); @@ -1513,7 +1533,8 @@ TEST(tool_snippet_short_name) { double ms; char *r = call_tool_timed("get_code_snippet", &ms, "{\"project\":\"%s\"," - "\"qualified_name\":\"incr_test_injected\"}", g_project); + "\"qualified_name\":\"incr_test_injected\"}", + g_project); TOOL_OK(r, ms); /* Should return source or suggestions */ ASSERT(strstr(r, "def") != NULL || strstr(r, "return") != NULL || @@ -1526,7 +1547,8 @@ TEST(tool_snippet_nonexistent) { double ms; char *r = call_tool_timed("get_code_snippet", &ms, "{\"project\":\"%s\"," - "\"qualified_name\":\"nonexistent_func_xyz\"}", g_project); + "\"qualified_name\":\"nonexistent_func_xyz\"}", + g_project); TOOL_OK(r, ms); /* Should return not found or suggestions, not crash */ free(r); @@ -1542,7 +1564,8 @@ TEST(tool_snippet_include_neighbors) { char *r = call_tool_timed("get_code_snippet", &ms, "{\"project\":\"%s\"," "\"qualified_name\":\"incr_test_injected\"," - "\"include_neighbors\":true}", g_project); + "\"include_neighbors\":true}", + g_project); TOOL_OK(r, ms); /* Must have source code */ ASSERT(resp_has_key(r, "source")); @@ -1552,14 +1575,15 @@ TEST(tool_snippet_include_neighbors) { PASS(); } -/* ── trace_call_path ───────────────────────────────────────────── */ +/* ── trace_path ───────────────────────────────────────────── */ TEST(tool_trace_outbound) { double ms; - char *r = call_tool_timed("trace_call_path", &ms, + char *r = call_tool_timed("trace_path", &ms, "{\"project\":\"%s\"," "\"function_name\":\"incr_test_injected\"," - "\"direction\":\"outbound\"}", g_project); + "\"direction\":\"outbound\"}", + g_project); TOOL_OK(r, ms); NOT_ERROR(r); /* direction=outbound → response has "callees", no "callers" */ @@ -1572,10 +1596,11 @@ TEST(tool_trace_outbound) { TEST(tool_trace_inbound) { double ms; - char *r = call_tool_timed("trace_call_path", &ms, + char *r = call_tool_timed("trace_path", &ms, "{\"project\":\"%s\"," "\"function_name\":\"incr_test_injected\"," - "\"direction\":\"inbound\"}", g_project); + "\"direction\":\"inbound\"}", + g_project); TOOL_OK(r, ms); NOT_ERROR(r); /* direction=inbound → response has "callers", no "callees" */ @@ -1587,10 +1612,11 @@ TEST(tool_trace_inbound) { TEST(tool_trace_both) { double ms; - char *r = call_tool_timed("trace_call_path", &ms, + char *r = call_tool_timed("trace_path", &ms, "{\"project\":\"%s\"," "\"function_name\":\"incr_test_injected\"," - "\"direction\":\"both\",\"depth\":5}", g_project); + "\"direction\":\"both\",\"depth\":5}", + g_project); TOOL_OK(r, ms); NOT_ERROR(r); /* direction=both → response has both "callers" and "callees" */ @@ -1602,10 +1628,11 @@ TEST(tool_trace_both) { TEST(tool_trace_depth_1) { double ms; - char *r = call_tool_timed("trace_call_path", &ms, + char *r = call_tool_timed("trace_path", &ms, "{\"project\":\"%s\"," "\"function_name\":\"incr_test_injected\"," - "\"direction\":\"both\",\"depth\":1}", g_project); + "\"direction\":\"both\",\"depth\":1}", + g_project); TOOL_OK(r, ms); NOT_ERROR(r); free(r); @@ -1614,10 +1641,11 @@ TEST(tool_trace_depth_1) { TEST(tool_trace_nonexistent) { double ms; - char *r = call_tool_timed("trace_call_path", &ms, + char *r = call_tool_timed("trace_path", &ms, "{\"project\":\"%s\"," "\"function_name\":\"no_such_function_xyz\"," - "\"direction\":\"both\"}", g_project); + "\"direction\":\"both\"}", + g_project); TOOL_OK(r, ms); /* Should return not found or empty, not crash */ free(r); @@ -1626,11 +1654,12 @@ TEST(tool_trace_nonexistent) { TEST(tool_trace_edge_types) { double ms; - char *r = call_tool_timed("trace_call_path", &ms, + char *r = call_tool_timed("trace_path", &ms, "{\"project\":\"%s\"," "\"function_name\":\"incr_test_injected\"," "\"direction\":\"both\"," - "\"edge_types\":[\"CALLS\"]}", g_project); + "\"edge_types\":[\"CALLS\"]}", + g_project); TOOL_OK(r, ms); NOT_ERROR(r); free(r); @@ -1651,8 +1680,8 @@ TEST(tool_arch_structure) { TEST(tool_arch_all) { double ms; - char *r = call_tool_timed("get_architecture", &ms, - "{\"project\":\"%s\",\"aspects\":[\"all\"]}", g_project); + char *r = call_tool_timed("get_architecture", &ms, "{\"project\":\"%s\",\"aspects\":[\"all\"]}", + g_project); TOOL_OK(r, ms); NOT_ERROR(r); free(r); @@ -1661,8 +1690,7 @@ TEST(tool_arch_all) { TEST(tool_arch_no_aspects) { double ms; - char *r = call_tool_timed("get_architecture", &ms, - "{\"project\":\"%s\"}", g_project); + char *r = call_tool_timed("get_architecture", &ms, "{\"project\":\"%s\"}", g_project); TOOL_OK(r, ms); NOT_ERROR(r); free(r); @@ -1673,8 +1701,7 @@ TEST(tool_arch_no_aspects) { TEST(tool_detect_changes_default) { double ms; - char *r = call_tool_timed("detect_changes", &ms, - "{\"project\":\"%s\"}", g_project); + char *r = call_tool_timed("detect_changes", &ms, "{\"project\":\"%s\"}", g_project); TOOL_OK(r, ms); /* Must have changed_files array and changed_count */ ASSERT(resp_has_key(r, "changed_files")); @@ -1697,8 +1724,7 @@ TEST(tool_detect_changes_custom_branch) { TEST(tool_detect_changes_depth) { double ms; - char *r = call_tool_timed("detect_changes", &ms, - "{\"project\":\"%s\",\"depth\":5}", g_project); + char *r = call_tool_timed("detect_changes", &ms, "{\"project\":\"%s\",\"depth\":5}", g_project); TOOL_OK(r, ms); free(r); PASS(); @@ -1708,8 +1734,8 @@ TEST(tool_detect_changes_depth) { TEST(tool_adr_get) { double ms; - char *r = call_tool_timed("manage_adr", &ms, - "{\"project\":\"%s\",\"mode\":\"get\"}", g_project); + char *r = + call_tool_timed("manage_adr", &ms, "{\"project\":\"%s\",\"mode\":\"get\"}", g_project); TOOL_OK(r, ms); /* May return empty if no ADR exists yet */ free(r); @@ -1718,8 +1744,8 @@ TEST(tool_adr_get) { TEST(tool_adr_sections) { double ms; - char *r = call_tool_timed("manage_adr", &ms, - "{\"project\":\"%s\",\"mode\":\"sections\"}", g_project); + char *r = + call_tool_timed("manage_adr", &ms, "{\"project\":\"%s\",\"mode\":\"sections\"}", g_project); TOOL_OK(r, ms); free(r); PASS(); @@ -1729,8 +1755,8 @@ TEST(tool_adr_sections) { TEST(tool_ingest_traces_empty) { double ms; - char *r = call_tool_timed("ingest_traces", &ms, - "{\"project\":\"%s\",\"traces\":[]}", g_project); + char *r = + call_tool_timed("ingest_traces", &ms, "{\"project\":\"%s\",\"traces\":[]}", g_project); TOOL_OK(r, ms); free(r); PASS(); @@ -1738,9 +1764,9 @@ TEST(tool_ingest_traces_empty) { TEST(tool_ingest_traces_basic) { double ms; - char *r = call_tool_timed("ingest_traces", &ms, - "{\"project\":\"%s\",\"traces\":[{\"caller\":\"a\",\"callee\":\"b\"}]}", - g_project); + char *r = call_tool_timed( + "ingest_traces", &ms, + "{\"project\":\"%s\",\"traces\":[{\"caller\":\"a\",\"callee\":\"b\"}]}", g_project); TOOL_OK(r, ms); free(r); PASS(); @@ -1761,8 +1787,7 @@ TEST(tool_err_query_bad_project) { TEST(tool_err_search_code_bad_project) { double ms; - char *r = call_tool_timed("search_code", &ms, - "{\"project\":\"xxx\",\"pattern\":\"test\"}"); + char *r = call_tool_timed("search_code", &ms, "{\"project\":\"xxx\",\"pattern\":\"test\"}"); TOOL_OK(r, ms); free(r); PASS(); @@ -1779,7 +1804,7 @@ TEST(tool_err_snippet_bad_project) { TEST(tool_err_trace_bad_project) { double ms; - char *r = call_tool_timed("trace_call_path", &ms, + char *r = call_tool_timed("trace_path", &ms, "{\"project\":\"xxx\"," "\"function_name\":\"test\",\"direction\":\"both\"}"); TOOL_OK(r, ms); @@ -1789,8 +1814,7 @@ TEST(tool_err_trace_bad_project) { TEST(tool_err_arch_bad_project) { double ms; - char *r = call_tool_timed("get_architecture", &ms, - "{\"project\":\"xxx\"}"); + char *r = call_tool_timed("get_architecture", &ms, "{\"project\":\"xxx\"}"); TOOL_OK(r, ms); free(r); PASS(); @@ -1798,8 +1822,7 @@ TEST(tool_err_arch_bad_project) { TEST(tool_err_detect_bad_project) { double ms; - char *r = call_tool_timed("detect_changes", &ms, - "{\"project\":\"xxx\"}"); + char *r = call_tool_timed("detect_changes", &ms, "{\"project\":\"xxx\"}"); TOOL_OK(r, ms); free(r); PASS(); @@ -1807,8 +1830,7 @@ TEST(tool_err_detect_bad_project) { TEST(tool_err_delete_nonexistent) { double ms; - char *r = call_tool_timed("delete_project", &ms, - "{\"project\":\"xxx-nonexistent\"}"); + char *r = call_tool_timed("delete_project", &ms, "{\"project\":\"xxx-nonexistent\"}"); TOOL_OK(r, ms); free(r); PASS(); @@ -1818,8 +1840,8 @@ TEST(tool_err_delete_nonexistent) { TEST(tool_index_mode_fast) { double ms; - char *r = call_tool_timed("index_repository", &ms, - "{\"repo_path\":\"%s\",\"mode\":\"fast\"}", g_repodir); + char *r = call_tool_timed("index_repository", &ms, "{\"repo_path\":\"%s\",\"mode\":\"fast\"}", + g_repodir); TOOL_OK(r, ms); ASSERT(strstr(r, "indexed") != NULL); free(r); @@ -1828,8 +1850,7 @@ TEST(tool_index_mode_fast) { TEST(tool_index_invalid_path) { double ms; - char *r = call_tool_timed("index_repository", &ms, - "{\"repo_path\":\"/nonexistent/path/xyz\"}"); + char *r = call_tool_timed("index_repository", &ms, "{\"repo_path\":\"/nonexistent/path/xyz\"}"); TOOL_OK(r, ms); /* Should fail gracefully */ free(r); @@ -1923,7 +1944,8 @@ TEST(tool_qg_count_functions) { char *r = call_tool_timed("query_graph", &ms, "{\"project\":\"%s\"," "\"query\":\"MATCH (n:Function) RETURN n.name LIMIT 200\"," - "\"max_rows\":200}", g_project); + "\"max_rows\":200}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_GT(total, 50); @@ -1933,10 +1955,11 @@ TEST(tool_qg_count_functions) { TEST(tool_qg_edge_properties) { double ms; - char *r = call_tool_timed("query_graph", &ms, - "{\"project\":\"%s\"," - "\"query\":\"MATCH (a)-[r:CALLS]->(b) RETURN a.name, b.name, r.callee LIMIT 5\"}", - g_project); + char *r = call_tool_timed( + "query_graph", &ms, + "{\"project\":\"%s\"," + "\"query\":\"MATCH (a)-[r:CALLS]->(b) RETURN a.name, b.name, r.callee LIMIT 5\"}", + g_project); TOOL_OK(r, ms); ASSERT(strstr(r, "columns") != NULL || strstr(r, "rows") != NULL); free(r); @@ -1947,7 +1970,8 @@ TEST(tool_qg_node_file_path) { double ms; char *r = call_tool_timed("query_graph", &ms, "{\"project\":\"%s\"," - "\"query\":\"MATCH (n:Function) WHERE n.name = 'incr_test_injected' RETURN n.name, n.file_path, n.start_line\"}", + "\"query\":\"MATCH (n:Function) WHERE n.name = 'incr_test_injected' " + "RETURN n.name, n.file_path, n.start_line\"}", g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); @@ -1959,10 +1983,11 @@ TEST(tool_qg_node_file_path) { TEST(tool_qg_match_module_defines) { double ms; /* DEFINES edges connect modules to their symbols */ - char *r = call_tool_timed("query_graph", &ms, - "{\"project\":\"%s\"," - "\"query\":\"MATCH (a)-[:DEFINES]->(b) RETURN a.name, b.name LIMIT 10\"}", - g_project); + char *r = + call_tool_timed("query_graph", &ms, + "{\"project\":\"%s\"," + "\"query\":\"MATCH (a)-[:DEFINES]->(b) RETURN a.name, b.name LIMIT 10\"}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_GT(total, 0); @@ -1975,7 +2000,8 @@ TEST(tool_qg_max_rows_1) { char *r = call_tool_timed("query_graph", &ms, "{\"project\":\"%s\"," "\"query\":\"MATCH (n:Function) RETURN n.name\"," - "\"max_rows\":1}", g_project); + "\"max_rows\":1}", + g_project); TOOL_OK(r, ms); int total = count_in_response(r, "total"); ASSERT_EQ(total, 1); @@ -1989,7 +2015,8 @@ TEST(tool_sc_limit_1) { double ms; char *r = call_tool_timed("search_code", &ms, "{\"project\":\"%s\"," - "\"pattern\":\"def \",\"limit\":1}", g_project); + "\"pattern\":\"def \",\"limit\":1}", + g_project); TOOL_OK(r, ms); NOT_ERROR(r); free(r); @@ -2000,7 +2027,8 @@ TEST(tool_sc_limit_50) { double ms; char *r = call_tool_timed("search_code", &ms, "{\"project\":\"%s\"," - "\"pattern\":\"import\",\"limit\":50}", g_project); + "\"pattern\":\"import\",\"limit\":50}", + g_project); TOOL_OK(r, ms); NOT_ERROR(r); free(r); @@ -2017,7 +2045,8 @@ TEST(tool_sc_combined) { "\"mode\":\"compact\"," "\"file_pattern\":\"*.py\"," "\"path_filter\":\"fastapi/\"," - "\"limit\":5,\"context\":2}", g_project); + "\"limit\":5,\"context\":2}", + g_project); TOOL_OK(r, ms); NOT_ERROR(r); free(r); @@ -2042,9 +2071,11 @@ TEST(tool_snippet_full_qn) { if (qn_start) { qn_start += 2; /* skip :" */ /* Handle escaped quotes in JSON */ - if (*qn_start == '\\') qn_start += 2; + if (*qn_start == '\\') + qn_start += 2; const char *qn_end = strchr(qn_start, '"'); - if (!qn_end) qn_end = strchr(qn_start, '\\'); + if (!qn_end) + qn_end = strchr(qn_start, '\\'); if (qn_end && qn_end > qn_start) { char qn[512]; int len = (int)(qn_end - qn_start); @@ -2101,9 +2132,10 @@ TEST(tool_arch_routes) { TEST(tool_arch_multi_aspects) { double ms; - char *r = call_tool_timed("get_architecture", &ms, - "{\"project\":\"%s\",\"aspects\":[\"structure\",\"dependencies\",\"entry_points\"]}", - g_project); + char *r = call_tool_timed( + "get_architecture", &ms, + "{\"project\":\"%s\",\"aspects\":[\"structure\",\"dependencies\",\"entry_points\"]}", + g_project); TOOL_OK(r, ms); NOT_ERROR(r); free(r); @@ -2114,8 +2146,8 @@ TEST(tool_arch_multi_aspects) { TEST(tool_detect_changes_scope) { double ms; - char *r = call_tool_timed("detect_changes", &ms, - "{\"project\":\"%s\",\"scope\":\"fastapi/\"}", g_project); + char *r = call_tool_timed("detect_changes", &ms, "{\"project\":\"%s\",\"scope\":\"fastapi/\"}", + g_project); TOOL_OK(r, ms); free(r); PASS(); @@ -2133,8 +2165,7 @@ TEST(tool_adr_update) { free(r); /* Verify it was stored */ - r = call_tool_timed("manage_adr", &ms, - "{\"project\":\"%s\",\"mode\":\"get\"}", g_project); + r = call_tool_timed("manage_adr", &ms, "{\"project\":\"%s\",\"mode\":\"get\"}", g_project); TOOL_OK(r, ms); ASSERT(strstr(r, "Test ADR") != NULL); free(r); @@ -2161,17 +2192,18 @@ TEST(tool_ingest_traces_multiple) { "{\"caller\":\"funcA\",\"callee\":\"funcB\",\"count\":5}," "{\"caller\":\"funcB\",\"callee\":\"funcC\",\"count\":3}," "{\"caller\":\"funcC\",\"callee\":\"funcD\",\"count\":1}" - "]}", g_project); + "]}", + g_project); TOOL_OK(r, ms); free(r); PASS(); } -/* ── trace_call_path: more edge_types combos ───────────────────── */ +/* ── trace_path: more edge_types combos ───────────────────── */ TEST(tool_trace_imports_only) { double ms; - char *r = call_tool_timed("trace_call_path", &ms, + char *r = call_tool_timed("trace_path", &ms, "{\"project\":\"%s\"," "\"function_name\":\"incr_test_injected\"," "\"direction\":\"both\"," @@ -2185,7 +2217,7 @@ TEST(tool_trace_imports_only) { TEST(tool_trace_calls_and_imports) { double ms; - char *r = call_tool_timed("trace_call_path", &ms, + char *r = call_tool_timed("trace_path", &ms, "{\"project\":\"%s\"," "\"function_name\":\"incr_test_injected\"," "\"direction\":\"both\"," @@ -2199,7 +2231,7 @@ TEST(tool_trace_calls_and_imports) { TEST(tool_trace_deep) { double ms; - char *r = call_tool_timed("trace_call_path", &ms, + char *r = call_tool_timed("trace_path", &ms, "{\"project\":\"%s\"," "\"function_name\":\"incr_test_injected\"," "\"direction\":\"outbound\",\"depth\":10}", @@ -2220,7 +2252,8 @@ TEST(tool_sg_rel_imports) { double ms; char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Module\"," - "\"relationship\":\"IMPORTS\",\"min_degree\":1}", g_project); + "\"relationship\":\"IMPORTS\",\"min_degree\":1}", + g_project); TOOL_OK(r, ms); int t = count_in_response(r, "total"); ASSERT_GT(t, 0); @@ -2232,7 +2265,8 @@ TEST(tool_sg_rel_defines) { double ms; char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Module\"," - "\"relationship\":\"DEFINES\",\"min_degree\":1}", g_project); + "\"relationship\":\"DEFINES\",\"min_degree\":1}", + g_project); TOOL_OK(r, ms); int t = count_in_response(r, "total"); ASSERT_GT(t, 0); @@ -2244,7 +2278,8 @@ TEST(tool_sg_rel_contains_file) { double ms; char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Folder\"," - "\"relationship\":\"CONTAINS_FILE\",\"min_degree\":1}", g_project); + "\"relationship\":\"CONTAINS_FILE\",\"min_degree\":1}", + g_project); TOOL_OK(r, ms); free(r); PASS(); @@ -2254,8 +2289,8 @@ TEST(tool_sg_rel_contains_file) { TEST(tool_sg_label_file) { double ms; - char *r = call_tool_timed("search_graph", &ms, - "{\"project\":\"%s\",\"label\":\"File\"}", g_project); + char *r = + call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"File\"}", g_project); TOOL_OK(r, ms); free(r); PASS(); @@ -2263,8 +2298,8 @@ TEST(tool_sg_label_file) { TEST(tool_sg_label_folder) { double ms; - char *r = call_tool_timed("search_graph", &ms, - "{\"project\":\"%s\",\"label\":\"Folder\"}", g_project); + char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Folder\"}", + g_project); TOOL_OK(r, ms); free(r); PASS(); @@ -2272,8 +2307,8 @@ TEST(tool_sg_label_folder) { TEST(tool_sg_label_package) { double ms; - char *r = call_tool_timed("search_graph", &ms, - "{\"project\":\"%s\",\"label\":\"Package\"}", g_project); + char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Package\"}", + g_project); TOOL_OK(r, ms); free(r); PASS(); @@ -2286,7 +2321,8 @@ TEST(tool_sg_include_connected_false) { char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Function\"," "\"name_pattern\":\"incr_test_injected\"," - "\"include_connected\":false}", g_project); + "\"include_connected\":false}", + g_project); TOOL_OK(r, ms); int t = count_in_response(r, "total"); ASSERT_EQ(t, 1); @@ -2300,7 +2336,8 @@ TEST(tool_sg_qn_and_label) { double ms; char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Function\"," - "\"qn_pattern\":\".*applications.*\"}", g_project); + "\"qn_pattern\":\".*applications.*\"}", + g_project); TOOL_OK(r, ms); int t = count_in_response(r, "total"); ASSERT_GT(t, 0); @@ -2315,7 +2352,8 @@ TEST(tool_sg_file_and_name) { char *r = call_tool_timed("search_graph", &ms, "{\"project\":\"%s\",\"label\":\"Function\"," "\"file_pattern\":\"*fastapi*\"," - "\"name_pattern\":\".*\",\"limit\":5}", g_project); + "\"name_pattern\":\".*\",\"limit\":5}", + g_project); TOOL_OK(r, ms); free(r); PASS(); @@ -2325,10 +2363,11 @@ TEST(tool_sg_file_and_name) { TEST(tool_qg_configures) { double ms; - char *r = call_tool_timed("query_graph", &ms, - "{\"project\":\"%s\"," - "\"query\":\"MATCH (a)-[r:CONFIGURES]->(b) RETURN a.name, b.name LIMIT 5\"}", - g_project); + char *r = call_tool_timed( + "query_graph", &ms, + "{\"project\":\"%s\"," + "\"query\":\"MATCH (a)-[r:CONFIGURES]->(b) RETURN a.name, b.name LIMIT 5\"}", + g_project); TOOL_OK(r, ms); /* May have 0 results if no CONFIGURES edges in FastAPI */ ASSERT(strstr(r, "columns") != NULL || strstr(r, "rows") != NULL); @@ -2340,10 +2379,11 @@ TEST(tool_qg_configures) { TEST(tool_qg_handles) { double ms; - char *r = call_tool_timed("query_graph", &ms, - "{\"project\":\"%s\"," - "\"query\":\"MATCH (a)-[r:HANDLES]->(b) RETURN a.name, b.name LIMIT 5\"}", - g_project); + char *r = + call_tool_timed("query_graph", &ms, + "{\"project\":\"%s\"," + "\"query\":\"MATCH (a)-[r:HANDLES]->(b) RETURN a.name, b.name LIMIT 5\"}", + g_project); TOOL_OK(r, ms); ASSERT(strstr(r, "columns") != NULL || strstr(r, "rows") != NULL); free(r); @@ -2356,7 +2396,8 @@ TEST(tool_qg_defines_method) { double ms; char *r = call_tool_timed("query_graph", &ms, "{\"project\":\"%s\"," - "\"query\":\"MATCH (c:Class)-[r:DEFINES_METHOD]->(m:Method) RETURN c.name, m.name LIMIT 5\"}", + "\"query\":\"MATCH (c:Class)-[r:DEFINES_METHOD]->(m:Method) RETURN " + "c.name, m.name LIMIT 5\"}", g_project); TOOL_OK(r, ms); ASSERT(strstr(r, "columns") != NULL || strstr(r, "rows") != NULL); @@ -2368,10 +2409,11 @@ TEST(tool_qg_defines_method) { TEST(tool_qg_no_limit) { double ms; - char *r = call_tool_timed("query_graph", &ms, - "{\"project\":\"%s\"," - "\"query\":\"MATCH (n:Function) WHERE n.name = 'incr_test_injected' RETURN n.name\"}", - g_project); + char *r = call_tool_timed( + "query_graph", &ms, + "{\"project\":\"%s\"," + "\"query\":\"MATCH (n:Function) WHERE n.name = 'incr_test_injected' RETURN n.name\"}", + g_project); TOOL_OK(r, ms); int t = count_in_response(r, "total"); ASSERT_GT(t, 0); @@ -2383,10 +2425,11 @@ TEST(tool_qg_no_limit) { TEST(tool_qg_empty_result) { double ms; - char *r = call_tool_timed("query_graph", &ms, - "{\"project\":\"%s\"," - "\"query\":\"MATCH (n:Function) WHERE n.name = 'zzz_no_exist_zzz' RETURN n.name\"}", - g_project); + char *r = call_tool_timed( + "query_graph", &ms, + "{\"project\":\"%s\"," + "\"query\":\"MATCH (n:Function) WHERE n.name = 'zzz_no_exist_zzz' RETURN n.name\"}", + g_project); TOOL_OK(r, ms); int t = count_in_response(r, "total"); ASSERT_EQ(t, 0); @@ -2443,7 +2486,8 @@ TEST(tool_snippet_neighbors_false) { char *r = call_tool_timed("get_code_snippet", &ms, "{\"project\":\"%s\"," "\"qualified_name\":\"incr_test_injected\"," - "\"include_neighbors\":false}", g_project); + "\"include_neighbors\":false}", + g_project); TOOL_OK(r, ms); /* include_neighbors=false should NOT have "caller_names" field */ ASSERT(resp_lacks_key(r, "caller_names")); @@ -2460,35 +2504,38 @@ TEST(tool_snippet_class) { double ms; char *r = call_tool_timed("get_code_snippet", &ms, "{\"project\":\"%s\"," - "\"qualified_name\":\"FastAPI\"}", g_project); + "\"qualified_name\":\"FastAPI\"}", + g_project); TOOL_OK(r, ms); free(r); PASS(); } -/* ── trace_call_path: depth=0 ──────────────────────────────────── */ +/* ── trace_path: depth=0 ──────────────────────────────────── */ TEST(tool_trace_depth_0) { double ms; - char *r = call_tool_timed("trace_call_path", &ms, + char *r = call_tool_timed("trace_path", &ms, "{\"project\":\"%s\"," "\"function_name\":\"incr_test_injected\"," - "\"direction\":\"both\",\"depth\":0}", g_project); + "\"direction\":\"both\",\"depth\":0}", + g_project); TOOL_OK(r, ms); NOT_ERROR(r); free(r); PASS(); } -/* ── trace_call_path: edge_types=[DEFINES] ─────────────────────── */ +/* ── trace_path: edge_types=[DEFINES] ─────────────────────── */ TEST(tool_trace_defines_only) { double ms; - char *r = call_tool_timed("trace_call_path", &ms, + char *r = call_tool_timed("trace_path", &ms, "{\"project\":\"%s\"," "\"function_name\":\"incr_test_injected\"," "\"direction\":\"inbound\"," - "\"edge_types\":[\"DEFINES\"]}", g_project); + "\"edge_types\":[\"DEFINES\"]}", + g_project); TOOL_OK(r, ms); NOT_ERROR(r); free(r); @@ -2501,7 +2548,8 @@ TEST(tool_detect_nonexistent_branch) { double ms; char *r = call_tool_timed("detect_changes", &ms, "{\"project\":\"%s\"," - "\"base_branch\":\"nonexistent-branch-xyz\"}", g_project); + "\"base_branch\":\"nonexistent-branch-xyz\"}", + g_project); TOOL_OK(r, ms); /* Should handle gracefully — git diff may fail or return empty */ free(r); @@ -2512,8 +2560,7 @@ TEST(tool_detect_nonexistent_branch) { TEST(tool_detect_depth_1) { double ms; - char *r = call_tool_timed("detect_changes", &ms, - "{\"project\":\"%s\",\"depth\":1}", g_project); + char *r = call_tool_timed("detect_changes", &ms, "{\"project\":\"%s\",\"depth\":1}", g_project); TOOL_OK(r, ms); free(r); PASS(); @@ -2523,8 +2570,7 @@ TEST(tool_detect_depth_1) { TEST(tool_adr_default_mode) { double ms; - char *r = call_tool_timed("manage_adr", &ms, - "{\"project\":\"%s\"}", g_project); + char *r = call_tool_timed("manage_adr", &ms, "{\"project\":\"%s\"}", g_project); TOOL_OK(r, ms); free(r); PASS(); @@ -2536,7 +2582,8 @@ TEST(tool_adr_update_empty) { double ms; char *r = call_tool_timed("manage_adr", &ms, "{\"project\":\"%s\",\"mode\":\"update\"," - "\"content\":\"\"}", g_project); + "\"content\":\"\"}", + g_project); TOOL_OK(r, ms); free(r); PASS(); @@ -2546,9 +2593,9 @@ TEST(tool_adr_update_empty) { TEST(tool_ingest_traces_partial) { double ms; - char *r = call_tool_timed("ingest_traces", &ms, - "{\"project\":\"%s\",\"traces\":[{\"caller\":\"onlyA\"}]}", - g_project); + char *r = + call_tool_timed("ingest_traces", &ms, + "{\"project\":\"%s\",\"traces\":[{\"caller\":\"onlyA\"}]}", g_project); TOOL_OK(r, ms); free(r); PASS(); @@ -2558,8 +2605,7 @@ TEST(tool_ingest_traces_partial) { TEST(tool_err_schema_bad_project) { double ms; - char *r = call_tool_timed("get_graph_schema", &ms, - "{\"project\":\"nonexistent-xyz\"}"); + char *r = call_tool_timed("get_graph_schema", &ms, "{\"project\":\"nonexistent-xyz\"}"); TOOL_OK(r, ms); free(r); PASS(); @@ -2579,8 +2625,8 @@ TEST(tool_err_index_status_no_project) { TEST(tool_err_adr_bad_project) { double ms; - char *r = call_tool_timed("manage_adr", &ms, - "{\"project\":\"nonexistent-xyz\",\"mode\":\"get\"}"); + char *r = + call_tool_timed("manage_adr", &ms, "{\"project\":\"nonexistent-xyz\",\"mode\":\"get\"}"); TOOL_OK(r, ms); free(r); PASS(); @@ -2590,8 +2636,8 @@ TEST(tool_err_adr_bad_project) { TEST(tool_err_ingest_bad_project) { double ms; - char *r = call_tool_timed("ingest_traces", &ms, - "{\"project\":\"nonexistent-xyz\",\"traces\":[]}"); + char *r = + call_tool_timed("ingest_traces", &ms, "{\"project\":\"nonexistent-xyz\",\"traces\":[]}"); TOOL_OK(r, ms); free(r); PASS(); @@ -2601,8 +2647,7 @@ TEST(tool_err_ingest_bad_project) { TEST(tool_err_ingest_no_traces) { double ms; - char *r = call_tool_timed("ingest_traces", &ms, - "{\"project\":\"%s\"}", g_project); + char *r = call_tool_timed("ingest_traces", &ms, "{\"project\":\"%s\"}", g_project); TOOL_OK(r, ms); free(r); PASS(); @@ -2612,8 +2657,8 @@ TEST(tool_err_ingest_no_traces) { TEST(tool_index_mode_full) { double ms; - char *r = call_tool_timed("index_repository", &ms, - "{\"repo_path\":\"%s\",\"mode\":\"full\"}", g_repodir); + char *r = call_tool_timed("index_repository", &ms, "{\"repo_path\":\"%s\",\"mode\":\"full\"}", + g_repodir); TOOL_OK(r, ms); ASSERT(strstr(r, "indexed") != NULL); free(r); @@ -2624,8 +2669,8 @@ TEST(tool_index_mode_full) { TEST(tool_arch_empty_aspects) { double ms; - char *r = call_tool_timed("get_architecture", &ms, - "{\"project\":\"%s\",\"aspects\":[]}", g_project); + char *r = + call_tool_timed("get_architecture", &ms, "{\"project\":\"%s\",\"aspects\":[]}", g_project); TOOL_OK(r, ms); NOT_ERROR(r); free(r); @@ -2643,11 +2688,13 @@ TEST(tool_delete_and_verify) { char pypath[512]; snprintf(pypath, sizeof(pypath), "%s/dummy.py", tmpdir2); FILE *f = fopen(pypath, "w"); - if (f) { fprintf(f, "def throwaway():\n pass\n"); fclose(f); } + if (f) { + fprintf(f, "def throwaway():\n pass\n"); + fclose(f); + } double ms; - char *r = call_tool_timed("index_repository", &ms, - "{\"repo_path\":\"%s\"}", tmpdir2); + char *r = call_tool_timed("index_repository", &ms, "{\"repo_path\":\"%s\"}", tmpdir2); TOOL_OK(r, ms); free(r); @@ -2655,21 +2702,18 @@ TEST(tool_delete_and_verify) { ASSERT(throwaway_project != NULL); /* Verify it exists */ - r = call_tool_timed("index_status", &ms, - "{\"project\":\"%s\"}", throwaway_project); + r = call_tool_timed("index_status", &ms, "{\"project\":\"%s\"}", throwaway_project); TOOL_OK(r, ms); free(r); /* Delete it */ - r = call_tool_timed("delete_project", &ms, - "{\"project\":\"%s\"}", throwaway_project); + r = call_tool_timed("delete_project", &ms, "{\"project\":\"%s\"}", throwaway_project); TOOL_OK(r, ms); ASSERT(strstr(r, "deleted") != NULL); free(r); /* Verify it's gone */ - r = call_tool_timed("index_status", &ms, - "{\"project\":\"%s\"}", throwaway_project); + r = call_tool_timed("index_status", &ms, "{\"project\":\"%s\"}", throwaway_project); TOOL_OK(r, ms); ASSERT(strstr(r, "not") != NULL || strstr(r, "error") != NULL); free(r); @@ -2782,7 +2826,7 @@ SUITE(incremental) { RUN_TEST(tool_snippet_nonexistent); RUN_TEST(tool_snippet_include_neighbors); - /* Phase 13: trace_call_path — directions + params */ + /* Phase 13: trace_path — directions + params */ RUN_TEST(tool_trace_outbound); RUN_TEST(tool_trace_inbound); RUN_TEST(tool_trace_both); @@ -2860,7 +2904,7 @@ SUITE(incremental) { /* Phase 27: ingest_traces multiple */ RUN_TEST(tool_ingest_traces_multiple); - /* Phase 28: trace_call_path edge_types combos */ + /* Phase 28: trace_path edge_types combos */ RUN_TEST(tool_trace_imports_only); RUN_TEST(tool_trace_calls_and_imports); RUN_TEST(tool_trace_deep); @@ -2896,7 +2940,7 @@ SUITE(incremental) { RUN_TEST(tool_snippet_neighbors_false); RUN_TEST(tool_snippet_class); - /* Phase 35: trace_call_path remaining */ + /* Phase 35: trace_path remaining */ RUN_TEST(tool_trace_depth_0); RUN_TEST(tool_trace_defines_only); diff --git a/tests/test_integration.c b/tests/test_integration.c index 046cb856..d9837def 100644 --- a/tests/test_integration.c +++ b/tests/test_integration.c @@ -367,7 +367,7 @@ TEST(integ_mcp_get_architecture) { PASS(); } -TEST(integ_mcp_trace_call_path) { +TEST(integ_mcp_trace_path) { /* Trace outbound calls from Compute → should reach Add and Multiply */ char args[256]; snprintf(args, sizeof(args), @@ -375,7 +375,7 @@ TEST(integ_mcp_trace_call_path) { "\"direction\":\"outbound\",\"max_depth\":3}", g_project); - char *resp = call_tool("trace_call_path", args); + char *resp = call_tool("trace_path", args); ASSERT_NOT_NULL(resp); /* Should find the function and show some path */ /* Either finds the function, or returns not found if name doesn't match exactly */ @@ -554,7 +554,7 @@ SUITE(integration) { RUN_TEST(integ_mcp_query_graph_calls); RUN_TEST(integ_mcp_get_graph_schema); RUN_TEST(integ_mcp_get_architecture); - RUN_TEST(integ_mcp_trace_call_path); + RUN_TEST(integ_mcp_trace_path); RUN_TEST(integ_mcp_index_status); /* Store query validation */ diff --git a/tests/test_mcp.c b/tests/test_mcp.c index 77ec9c99..72729f11 100644 --- a/tests/test_mcp.c +++ b/tests/test_mcp.c @@ -133,7 +133,7 @@ TEST(mcp_tools_list) { ASSERT_NOT_NULL(strstr(json, "index_repository")); ASSERT_NOT_NULL(strstr(json, "search_graph")); ASSERT_NOT_NULL(strstr(json, "query_graph")); - ASSERT_NOT_NULL(strstr(json, "trace_call_path")); + ASSERT_NOT_NULL(strstr(json, "trace_path")); ASSERT_NOT_NULL(strstr(json, "get_code_snippet")); ASSERT_NOT_NULL(strstr(json, "get_graph_schema")); ASSERT_NOT_NULL(strstr(json, "get_architecture")); @@ -581,7 +581,8 @@ TEST(tool_search_code_no_project) { "\"project\":\"nonexistent\"}}}"); ASSERT_NOT_NULL(resp); /* No project indexed → error */ - ASSERT_TRUE(strstr(resp, "not found") || strstr(resp, "not indexed") || strstr(resp, "required")); + ASSERT_TRUE(strstr(resp, "not found") || strstr(resp, "not indexed") || + strstr(resp, "required")); free(resp); cbm_mcp_server_free(srv); @@ -1536,9 +1537,9 @@ TEST(server_handle_tools_call_missing_name) { cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL); /* tools/call with no tool name in params */ - char *resp = cbm_mcp_server_handle( - srv, "{\"jsonrpc\":\"2.0\",\"id\":50,\"method\":\"tools/call\"," - "\"params\":{\"arguments\":{}}}"); + char *resp = + cbm_mcp_server_handle(srv, "{\"jsonrpc\":\"2.0\",\"id\":50,\"method\":\"tools/call\"," + "\"params\":{\"arguments\":{}}}"); ASSERT_NOT_NULL(resp); /* Should return error about unknown/missing tool */ ASSERT_NOT_NULL(strstr(resp, "\"id\":50")); @@ -1577,11 +1578,10 @@ TEST(mcp_server_run_rapid_messages) { ASSERT_EQ(pipe(fds), 0); /* Write all 3 messages to the write end in one shot */ - const char *msgs = - "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\"," - "\"params\":{\"protocolVersion\":\"2025-11-25\",\"capabilities\":{}}}\n" - "{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}\n" - "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\",\"params\":{}}\n"; + const char *msgs = "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\"," + "\"params\":{\"protocolVersion\":\"2025-11-25\",\"capabilities\":{}}}\n" + "{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}\n" + "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\",\"params\":{}}\n"; ssize_t written = write(fds[1], msgs, strlen(msgs)); ASSERT_TRUE(written > 0); close(fds[1]); /* EOF signals end of input to the server */