mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-09 09:11:00 +08:00
Merge pull request #1319 from JJordan0C/fix/search-graph-semantic-only-results
fix(mcp): isolate semantic-only JSON search
This commit is contained in:
+31
-38
@@ -3170,22 +3170,6 @@ static bool run_semantic_query_core(const char *args, cbm_store_t *store, const
|
||||
return type_error;
|
||||
}
|
||||
|
||||
/* Append the semantic_query vector-search results onto the doc. Returns
|
||||
* true if semantic_query was provided as a non-array (type error — caller
|
||||
* should surface to the user). */
|
||||
static bool run_semantic_query(yyjson_mut_doc *doc, yyjson_mut_val *root, const char *args,
|
||||
cbm_store_t *store, const char *project, int limit) {
|
||||
cbm_vector_result_t *vresults = NULL;
|
||||
int vcount = 0;
|
||||
bool type_error =
|
||||
run_semantic_query_core(args, store, project, limit, &vresults, &vcount, NULL);
|
||||
if (vcount > 0) {
|
||||
emit_semantic_results(doc, root, vresults, vcount);
|
||||
cbm_store_free_vector_results(vresults, vcount);
|
||||
}
|
||||
return type_error;
|
||||
}
|
||||
|
||||
/* ── Tree output for search_graph ───────────────────────────────────
|
||||
* Default response encoding: grouped tree rows (compact_out.h). The same
|
||||
* model is available as structured JSON via format:"json"; include_connected
|
||||
@@ -3620,6 +3604,9 @@ static char *handle_search_graph(cbm_mcp_server_t *srv, const char *args) {
|
||||
.min_degree = min_degree,
|
||||
.max_degree = max_degree,
|
||||
};
|
||||
bool has_structural_filters = label || name_pattern || qn_pattern || file_pattern ||
|
||||
relationship || exclude_entry_points ||
|
||||
min_degree != CBM_NOT_FOUND || max_degree != CBM_NOT_FOUND;
|
||||
|
||||
if (!legacy_json) {
|
||||
const char *fields[SG_MAX_EXTRA_FIELDS];
|
||||
@@ -3640,10 +3627,7 @@ static char *handle_search_graph(cbm_mcp_server_t *srv, const char *args) {
|
||||
/* Semantic-only calls get semantic results only: the legacy
|
||||
* behavior also ran the UNFILTERED regex search and prepended
|
||||
* up to `limit` unrelated enriched nodes to the response. */
|
||||
bool has_filters = label || name_pattern || qn_pattern || file_pattern ||
|
||||
relationship || exclude_entry_points ||
|
||||
min_degree != CBM_NOT_FOUND || max_degree != CBM_NOT_FOUND;
|
||||
bool semantic_only = sq_present && !has_filters;
|
||||
bool semantic_only = sq_present && !has_structural_filters;
|
||||
|
||||
cbm_sb_t sb;
|
||||
cbm_sb_init(&sb);
|
||||
@@ -3727,8 +3711,31 @@ static char *handle_search_graph(cbm_mcp_server_t *srv, const char *args) {
|
||||
true);
|
||||
}
|
||||
|
||||
cbm_vector_result_t *vresults = NULL;
|
||||
int vcount = 0;
|
||||
bool sq_present = false;
|
||||
bool sq_type_error =
|
||||
run_semantic_query_core(args, store, project, limit, &vresults, &vcount, &sq_present);
|
||||
if (sq_type_error) {
|
||||
free(project);
|
||||
free(label);
|
||||
free(name_pattern);
|
||||
free(qn_pattern);
|
||||
free(file_pattern);
|
||||
free(relationship);
|
||||
return cbm_mcp_text_result(
|
||||
"semantic_query must be an array of keyword strings, e.g. "
|
||||
"[\"send\",\"pubsub\",\"publish\"] — not a single string. Split your query "
|
||||
"into individual keywords; each is scored independently via per-keyword "
|
||||
"min-cosine.",
|
||||
true);
|
||||
}
|
||||
|
||||
bool semantic_only = sq_present && !has_structural_filters;
|
||||
cbm_search_output_t out = {0};
|
||||
cbm_store_search(store, ¶ms, &out);
|
||||
if (!semantic_only) {
|
||||
cbm_store_search(store, ¶ms, &out);
|
||||
}
|
||||
|
||||
yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL);
|
||||
yyjson_mut_val *root = yyjson_mut_obj(doc);
|
||||
@@ -3767,23 +3774,9 @@ static char *handle_search_graph(cbm_mcp_server_t *srv, const char *args) {
|
||||
}
|
||||
}
|
||||
|
||||
bool sq_type_error = run_semantic_query(doc, root, args, store, project, limit);
|
||||
|
||||
if (sq_type_error) {
|
||||
yyjson_mut_doc_free(doc);
|
||||
cbm_store_search_free(&out);
|
||||
free(project);
|
||||
free(label);
|
||||
free(name_pattern);
|
||||
free(qn_pattern);
|
||||
free(file_pattern);
|
||||
free(relationship);
|
||||
return cbm_mcp_text_result(
|
||||
"semantic_query must be an array of keyword strings, e.g. "
|
||||
"[\"send\",\"pubsub\",\"publish\"] — not a single string. Split your query "
|
||||
"into individual keywords; each is scored independently via per-keyword "
|
||||
"min-cosine.",
|
||||
true);
|
||||
if (vcount > 0) {
|
||||
emit_semantic_results(doc, root, vresults, vcount);
|
||||
cbm_store_free_vector_results(vresults, vcount);
|
||||
}
|
||||
|
||||
char *json = yy_doc_to_str(doc);
|
||||
|
||||
@@ -1637,6 +1637,50 @@ static cbm_mcp_server_t *setup_snippet_server(char *tmp_dir, size_t tmp_sz);
|
||||
static void cleanup_snippet_dir(const char *tmp_dir);
|
||||
static char *extract_text_content(const char *mcp_result);
|
||||
|
||||
TEST(tool_search_graph_semantic_only_skips_structural_results_issue1295) {
|
||||
cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
|
||||
ASSERT_NOT_NULL(srv);
|
||||
cbm_store_t *st = cbm_mcp_server_store(srv);
|
||||
const char *proj = "semantic-only";
|
||||
cbm_mcp_server_set_project(srv, proj);
|
||||
cbm_store_upsert_project(st, proj, "/tmp/semantic-only");
|
||||
|
||||
cbm_node_t unrelated = {.project = proj,
|
||||
.label = "Function",
|
||||
.name = "unrelated_node",
|
||||
.qualified_name = "semantic-only.unrelated_node",
|
||||
.file_path = "unrelated.c",
|
||||
.start_line = 1,
|
||||
.end_line = 2};
|
||||
ASSERT_GT(cbm_store_upsert_node(st, &unrelated), 0);
|
||||
|
||||
char *resp = cbm_mcp_handle_tool(
|
||||
srv, "search_graph",
|
||||
"{\"project\":\"semantic-only\",\"semantic_query\":[\"publish\"],\"limit\":5}");
|
||||
ASSERT_NOT_NULL(resp);
|
||||
char *inner = extract_text_content(resp);
|
||||
free(resp);
|
||||
ASSERT_NOT_NULL(inner);
|
||||
ASSERT_NULL(strstr(inner, "unrelated_node"));
|
||||
free(inner);
|
||||
|
||||
resp = cbm_mcp_handle_tool(srv, "search_graph",
|
||||
"{\"project\":\"semantic-only\",\"semantic_query\":[\"publish\"],"
|
||||
"\"format\":\"json\",\"limit\":5}");
|
||||
ASSERT_NOT_NULL(resp);
|
||||
inner = extract_text_content(resp);
|
||||
free(resp);
|
||||
ASSERT_NOT_NULL(inner);
|
||||
ASSERT_NOT_NULL(strstr(inner, "\"total\":0"));
|
||||
ASSERT_NOT_NULL(strstr(inner, "\"count\":0"));
|
||||
ASSERT_NOT_NULL(strstr(inner, "\"groups\":[]"));
|
||||
ASSERT_NULL(strstr(inner, "unrelated_node"));
|
||||
free(inner);
|
||||
|
||||
cbm_mcp_server_free(srv);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* callers_total/callees_total must count what the caller can enumerate: with
|
||||
* include_tests=false (default) test-file rows are hidden from the table, so
|
||||
* the totals must apply the same filter — a raw visited_count overstated the
|
||||
@@ -10815,6 +10859,7 @@ SUITE(mcp) {
|
||||
RUN_TEST(tool_get_graph_schema_empty);
|
||||
RUN_TEST(tool_unknown_tool);
|
||||
RUN_TEST(tool_search_graph_basic);
|
||||
RUN_TEST(tool_search_graph_semantic_only_skips_structural_results_issue1295);
|
||||
RUN_TEST(tool_trace_totals_respect_test_filter);
|
||||
RUN_TEST(tool_trace_totals_respect_test_filter_tests_root_subtree_issue1294);
|
||||
RUN_TEST(tool_get_architecture_cycles_detects_scc);
|
||||
|
||||
Reference in New Issue
Block a user