mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 08:13:37 +08:00
fix(mcp): report dirty freshness for source search
Add dirty-file freshness metadata to search_code responses without hiding live source matches. The handler computes dirty counts once and passes them into the existing search_code JSON assembly path, avoiding post-hoc mutation of the outer MCP text wrapper. Cover the behavior with a focused MCP test that keeps the live source match visible, preserves dirty ledger state, and asserts the source-search-specific graph freshness warning. Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
+27
-6
@@ -189,8 +189,10 @@ static bool get_dirty_file_counts(cbm_store_t *store, const char *project,
|
||||
return true;
|
||||
}
|
||||
|
||||
static void add_dirty_file_freshness_counts(yyjson_mut_doc *doc, yyjson_mut_val *root,
|
||||
int pending, int overlay_ready) {
|
||||
static void add_dirty_file_freshness_counts_with_warning(yyjson_mut_doc *doc,
|
||||
yyjson_mut_val *root,
|
||||
int pending, int overlay_ready,
|
||||
const char *warning_message) {
|
||||
if (!doc || !root || (pending <= 0 && overlay_ready <= 0)) {
|
||||
return;
|
||||
}
|
||||
@@ -211,8 +213,15 @@ static void add_dirty_file_freshness_counts(yyjson_mut_doc *doc, yyjson_mut_val
|
||||
yyjson_mut_obj_add_int(doc, freshness, CBM_MCP_FRESHNESS_DIRTY_OVERLAY_READY_KEY,
|
||||
overlay_ready);
|
||||
add_response_warning(doc, root,
|
||||
"project has dirty files; canonical graph rows remain visible until "
|
||||
"overlay or reindex completes.");
|
||||
warning_message
|
||||
? warning_message
|
||||
: "project has dirty files; canonical graph rows remain visible until "
|
||||
"overlay or reindex completes.");
|
||||
}
|
||||
|
||||
static void add_dirty_file_freshness_counts(yyjson_mut_doc *doc, yyjson_mut_val *root,
|
||||
int pending, int overlay_ready) {
|
||||
add_dirty_file_freshness_counts_with_warning(doc, root, pending, overlay_ready, NULL);
|
||||
}
|
||||
|
||||
static void add_dirty_file_freshness(yyjson_mut_doc *doc, yyjson_mut_val *root,
|
||||
@@ -6350,7 +6359,8 @@ static char *assemble_search_output(search_result_t *sr, int sr_count, grep_matc
|
||||
int raw_count, int gm_count, int limit, int mode,
|
||||
int context_lines, const char *root_path,
|
||||
bool warn_literal_pipe, uint64_t elapsed_ms,
|
||||
const char *search_scope) {
|
||||
const char *search_scope, int dirty_pending,
|
||||
int dirty_overlay_ready, const char *dirty_warning) {
|
||||
enum {
|
||||
MODE_COMPACT = 0,
|
||||
MODE_FULL = 1,
|
||||
@@ -6442,6 +6452,8 @@ static char *assemble_search_output(search_result_t *sr, int sr_count, grep_matc
|
||||
if (yyjson_mut_arr_size(warnings) > 0) {
|
||||
yyjson_mut_obj_add_val(doc, root_obj, "warnings", warnings);
|
||||
}
|
||||
add_dirty_file_freshness_counts_with_warning(doc, root_obj, dirty_pending,
|
||||
dirty_overlay_ready, dirty_warning);
|
||||
|
||||
char *json = yy_doc_to_str(doc);
|
||||
if (json) {
|
||||
@@ -7112,10 +7124,19 @@ static char *handle_search_code(cbm_mcp_server_t *srv, const char *args) {
|
||||
? "path_filter_exact"
|
||||
: (scan_mode == SEARCH_CODE_SCAN_GIT_GREP ? "git_worktree"
|
||||
: "project_recursive");
|
||||
int dirty_pending = 0;
|
||||
int dirty_overlay_ready = 0;
|
||||
if (!get_dirty_file_counts(store, project, &dirty_pending, &dirty_overlay_ready) &&
|
||||
srv->store && srv->store != store) {
|
||||
get_dirty_file_counts(srv->store, project, &dirty_pending, &dirty_overlay_ready);
|
||||
}
|
||||
char *result =
|
||||
assemble_search_output(sr, sr_count, raw, raw_count, gm_count, limit, mode, context_lines,
|
||||
root_path, pat_has_pipe && !use_regex, cbm_now_ms() - search_t0,
|
||||
search_scope);
|
||||
search_scope, dirty_pending, dirty_overlay_ready,
|
||||
"search_code reads live source files, but graph annotations use "
|
||||
"canonical graph rows; dirty file graph metadata may be absent "
|
||||
"until overlay or reindex completes.");
|
||||
free(gm);
|
||||
free(sr);
|
||||
free(raw);
|
||||
|
||||
@@ -2295,6 +2295,53 @@ TEST(search_code_multi_word) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(search_code_reports_dirty_graph_metadata_without_hiding_live_matches) {
|
||||
char tmp[512];
|
||||
cbm_mcp_server_t *srv = setup_snippet_server(tmp, sizeof(tmp));
|
||||
ASSERT_NOT_NULL(srv);
|
||||
cbm_store_t *st = cbm_mcp_server_store(srv);
|
||||
ASSERT_NOT_NULL(st);
|
||||
|
||||
cbm_dirty_file_state_t dirty = {.project = "test-project",
|
||||
.rel_path = "main.go",
|
||||
.observed_hash = "search-code-dirty-hash",
|
||||
.observed_generation = 16,
|
||||
.source = CBM_STORE_DIRTY_SOURCE_EXPLICIT_REINDEX,
|
||||
.status = CBM_STORE_DIRTY_STATUS_PENDING};
|
||||
ASSERT_EQ(cbm_store_upsert_dirty_file(st, &dirty), CBM_STORE_OK);
|
||||
int pending = 0;
|
||||
int overlay_ready = 0;
|
||||
ASSERT_EQ(cbm_store_count_dirty_files(st, "test-project", &pending, &overlay_ready),
|
||||
CBM_STORE_OK);
|
||||
ASSERT_EQ(pending, 1);
|
||||
ASSERT_EQ(overlay_ready, 0);
|
||||
|
||||
char *resp = cbm_mcp_server_handle(
|
||||
srv, "{\"jsonrpc\":\"2.0\",\"id\":91,\"method\":\"tools/call\","
|
||||
"\"params\":{\"name\":\"search_code\","
|
||||
"\"arguments\":{\"pattern\":\"HandleRequest\",\"project\":\"test-project\"}}}");
|
||||
ASSERT_NOT_NULL(resp);
|
||||
char *inner = extract_text_content(resp);
|
||||
ASSERT_NOT_NULL(inner);
|
||||
pending = 0;
|
||||
overlay_ready = 0;
|
||||
ASSERT_EQ(cbm_store_count_dirty_files(cbm_mcp_server_store(srv), "test-project", &pending,
|
||||
&overlay_ready),
|
||||
CBM_STORE_OK);
|
||||
ASSERT_EQ(pending, 1);
|
||||
ASSERT_EQ(overlay_ready, 0);
|
||||
ASSERT_NOT_NULL(strstr(inner, "HandleRequest"));
|
||||
ASSERT_NOT_NULL(strstr(inner, "\"warnings\""));
|
||||
ASSERT_NOT_NULL(strstr(inner, "search_code reads live source files"));
|
||||
ASSERT_TRUE(has_dirty_freshness_counts(inner, 1, 0));
|
||||
|
||||
free(inner);
|
||||
free(resp);
|
||||
cleanup_snippet_dir(tmp);
|
||||
cbm_mcp_server_free(srv);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(search_code_limit_zero_uses_config_default) {
|
||||
char tmp[512];
|
||||
cbm_mcp_server_t *srv = setup_snippet_server(tmp, sizeof(tmp));
|
||||
@@ -4097,6 +4144,7 @@ SUITE(mcp) {
|
||||
RUN_TEST(tool_search_code_missing_pattern);
|
||||
RUN_TEST(tool_search_code_no_project);
|
||||
RUN_TEST(search_code_multi_word);
|
||||
RUN_TEST(search_code_reports_dirty_graph_metadata_without_hiding_live_matches);
|
||||
RUN_TEST(search_code_limit_zero_uses_config_default);
|
||||
RUN_TEST(search_code_invalid_regex_errors_issue283);
|
||||
RUN_TEST(search_code_literal_pipe_warns_issue282);
|
||||
|
||||
Reference in New Issue
Block a user