Schema properties, DATA_FLOWS, YAML nested extraction, trace_path tool
- Serialize return_type, parent_class, base_classes on definition nodes - DATA_FLOWS edges link callers through Route nodes to handlers - YAML nested field extraction with dotted key paths (key_path property) - Rename trace_call_path → trace_path with mode parameter: calls (default), data_flow, cross_service - Backward compatible: trace_call_path alias still accepted - Updated CLI help text and all tests
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
+9
-9
@@ -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) {
|
||||
|
||||
+1
-1
@@ -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");
|
||||
|
||||
+75
-9
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+297
-180
@@ -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"
|
||||
"<!-- codebase-memory-mcp:start -->\n"
|
||||
"OLD CONTENT\n"
|
||||
"<!-- codebase-memory-mcp:end -->\n"
|
||||
"# Other stuff\n");
|
||||
write_test_file(filepath, "# Rules\n"
|
||||
"<!-- codebase-memory-mcp:start -->\n"
|
||||
"OLD CONTENT\n"
|
||||
"<!-- codebase-memory-mcp:end -->\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"
|
||||
"<!-- codebase-memory-mcp:start -->\n"
|
||||
"CMM Content\n"
|
||||
"<!-- codebase-memory-mcp:end -->\n"
|
||||
"# Other\n");
|
||||
write_test_file(filepath, "# Rules\n"
|
||||
"<!-- codebase-memory-mcp:start -->\n"
|
||||
"CMM Content\n"
|
||||
"<!-- codebase-memory-mcp:end -->\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);
|
||||
|
||||
+320
-276
File diff suppressed because it is too large
Load Diff
@@ -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 */
|
||||
|
||||
+10
-10
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user