mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-08 08:53:43 +08:00
watcher: register all session-accessed projects for auto-reindex
Previously only the CWD project at startup was watched for file changes. Now any project the AI session interacts with gets registered: - handle_index_repository: call cbm_watcher_watch() after successful index+pagerank, so explicitly indexed projects get auto-reindexed on file changes (same pattern as auto-index thread at lines 3503-3505) - resolve_store: call cbm_store_get_project() to get root_path from DB, then cbm_watcher_watch() when a new store is opened. Only runs on the new-store path (early-return skips already-cached projects). Covers all data-access tool paths: search_code_graph, trace_call_path, get_code. TDD: 3 new tests in test_tool_consolidation.c (all pass, 2193 total): watcher_registered_after_index_repository watcher_registered_on_resolve_store watcher_not_registered_for_unknown_path Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
@@ -914,6 +914,15 @@ static cbm_store_t *resolve_store(cbm_mcp_server_t *srv, const char *project) {
|
||||
srv->owns_store = true;
|
||||
free(srv->current_project);
|
||||
srv->current_project = heap_strdup(db_project);
|
||||
/* Register newly-accessed project with watcher (root_path from DB) */
|
||||
if (srv->watcher && srv->store) {
|
||||
cbm_project_t proj = {0};
|
||||
if (cbm_store_get_project(srv->store, db_project, &proj) == CBM_STORE_OK
|
||||
&& proj.root_path && proj.root_path[0]) {
|
||||
cbm_watcher_watch(srv->watcher, db_project, proj.root_path);
|
||||
cbm_project_free_fields(&proj); /* store.h:578 */
|
||||
}
|
||||
}
|
||||
|
||||
return srv->store;
|
||||
}
|
||||
@@ -2266,6 +2275,9 @@ static char *handle_index_repository(cbm_mcp_server_t *srv, const char *args) {
|
||||
/* Compute PageRank + LinkRank on full graph (project + deps).
|
||||
* Uses config-backed edge weights when config is available. */
|
||||
cbm_pagerank_compute_with_config(store, project_name, srv->config);
|
||||
/* Register project with watcher so future file changes trigger auto-reindex */
|
||||
if (srv->watcher)
|
||||
cbm_watcher_watch(srv->watcher, project_name, repo_path);
|
||||
|
||||
int nodes = cbm_store_count_nodes(store, project_name);
|
||||
int edges = cbm_store_count_edges(store, project_name);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <yyjson/yyjson.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <watcher/watcher.h>
|
||||
|
||||
/* ── 1. Tool visibility tests ─────────────────────────────── */
|
||||
|
||||
@@ -1311,6 +1312,104 @@ TEST(get_code_cold_start_parses_project_from_qn) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ── Watcher registration tests ──────────────────────────── */
|
||||
|
||||
TEST(watcher_registered_after_index_repository) {
|
||||
/* Create a tiny temp repo so indexing succeeds quickly */
|
||||
char repo_path[] = "/tmp/cbm_watch_test_XXXXXX";
|
||||
ASSERT_NOT_NULL(mkdtemp(repo_path));
|
||||
char src_path[256];
|
||||
snprintf(src_path, sizeof(src_path), "%s/test.c", repo_path);
|
||||
FILE *f = fopen(src_path, "w");
|
||||
if (f) { fprintf(f, "void hello(void) {}\n"); fclose(f); }
|
||||
|
||||
cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
|
||||
ASSERT_NOT_NULL(srv);
|
||||
cbm_watcher_t *w = cbm_watcher_new(NULL, NULL, NULL);
|
||||
ASSERT_NOT_NULL(w);
|
||||
cbm_mcp_server_set_watcher(srv, w);
|
||||
|
||||
char args[512];
|
||||
snprintf(args, sizeof(args), "{\"repo_path\":\"%s\"}", repo_path);
|
||||
char *resp = cbm_mcp_handle_tool(srv, "index_repository", args);
|
||||
ASSERT_NOT_NULL(resp);
|
||||
free(resp);
|
||||
|
||||
ASSERT_TRUE(cbm_watcher_watch_count(w) > 0);
|
||||
|
||||
cbm_mcp_server_free(srv);
|
||||
cbm_watcher_free(w);
|
||||
(void)unlink(src_path);
|
||||
(void)rmdir(repo_path);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(watcher_registered_on_resolve_store) {
|
||||
/* Pre-populate a DB with a project that has a known root_path */
|
||||
char db_path[1024];
|
||||
snprintf(db_path, sizeof(db_path), "%s/.cache/codebase-memory-mcp/_tc_watcher_.db",
|
||||
getenv("HOME"));
|
||||
cbm_store_t *s = cbm_store_open_path(db_path);
|
||||
ASSERT_NOT_NULL(s);
|
||||
cbm_store_upsert_project(s, "_tc_watcher_", "/tmp/cbm_watcher_root");
|
||||
cbm_node_t n = {.project = "_tc_watcher_", .label = "Function",
|
||||
.name = "watcher_fn", .qualified_name = "_tc_watcher_.watcher_fn",
|
||||
.file_path = "watcher_fn.c"};
|
||||
cbm_store_upsert_node(s, &n);
|
||||
cbm_store_close(s);
|
||||
|
||||
cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
|
||||
ASSERT_NOT_NULL(srv);
|
||||
cbm_watcher_t *w = cbm_watcher_new(NULL, NULL, NULL);
|
||||
ASSERT_NOT_NULL(w);
|
||||
cbm_mcp_server_set_watcher(srv, w);
|
||||
|
||||
char *resp = cbm_mcp_handle_tool(srv, "search_code_graph",
|
||||
"{\"project\":\"_tc_watcher_\",\"name_pattern\":\"watcher_fn\",\"limit\":1}");
|
||||
ASSERT_NOT_NULL(resp);
|
||||
free(resp);
|
||||
|
||||
ASSERT_TRUE(cbm_watcher_watch_count(w) > 0);
|
||||
|
||||
cbm_mcp_server_free(srv);
|
||||
cbm_watcher_free(w);
|
||||
(void)unlink(db_path);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(watcher_not_registered_for_unknown_path) {
|
||||
/* Project entry exists but root_path is empty — watcher must NOT be registered */
|
||||
char db_path[1024];
|
||||
snprintf(db_path, sizeof(db_path), "%s/.cache/codebase-memory-mcp/_tc_watcher_nopath_.db",
|
||||
getenv("HOME"));
|
||||
cbm_store_t *s = cbm_store_open_path(db_path);
|
||||
ASSERT_NOT_NULL(s);
|
||||
cbm_store_upsert_project(s, "_tc_watcher_nopath_", "");
|
||||
cbm_node_t n = {.project = "_tc_watcher_nopath_", .label = "Function",
|
||||
.name = "nopath_fn", .qualified_name = "_tc_watcher_nopath_.nopath_fn",
|
||||
.file_path = "nopath_fn.c"};
|
||||
cbm_store_upsert_node(s, &n);
|
||||
cbm_store_close(s);
|
||||
|
||||
cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
|
||||
ASSERT_NOT_NULL(srv);
|
||||
cbm_watcher_t *w = cbm_watcher_new(NULL, NULL, NULL);
|
||||
ASSERT_NOT_NULL(w);
|
||||
cbm_mcp_server_set_watcher(srv, w);
|
||||
|
||||
char *resp = cbm_mcp_handle_tool(srv, "search_code_graph",
|
||||
"{\"project\":\"_tc_watcher_nopath_\",\"name_pattern\":\"nopath_fn\",\"limit\":1}");
|
||||
ASSERT_NOT_NULL(resp);
|
||||
free(resp);
|
||||
|
||||
ASSERT_EQ(cbm_watcher_watch_count(w), 0);
|
||||
|
||||
cbm_mcp_server_free(srv);
|
||||
cbm_watcher_free(w);
|
||||
(void)unlink(db_path);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ── Suite registration ──────────────────────────────────── */
|
||||
|
||||
SUITE(tool_consolidation) {
|
||||
@@ -1395,4 +1494,7 @@ SUITE(tool_consolidation) {
|
||||
RUN_TEST(get_code_no_project_uses_open_store_tier1);
|
||||
RUN_TEST(get_code_single_fuzzy_result_resolves_not_ambiguous);
|
||||
RUN_TEST(get_code_cold_start_parses_project_from_qn);
|
||||
RUN_TEST(watcher_registered_after_index_repository);
|
||||
RUN_TEST(watcher_registered_on_resolve_store);
|
||||
RUN_TEST(watcher_not_registered_for_unknown_path);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user