Skip to content

Commit 54efcef

Browse files
authored
Merge pull request DeusData#849 from DeusData/distill/625-auto-watch
feat(watcher): auto_watch config gate for background watch registration (default on)
2 parents c737413 + aa15316 commit 54efcef

5 files changed

Lines changed: 158 additions & 7 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ codebase-memory-mcp config set auto_index true
122122

123123
When enabled, new projects are indexed automatically on first connection. Previously-indexed projects are registered with the background watcher for ongoing git-based change detection. Configurable file limit: `config set auto_index_limit 50000`.
124124

125+
Watcher registration is controlled separately by `auto_watch` (default `true`). Set `config set auto_watch false` to keep a session from registering its project with the background watcher — useful when working across many projects and you want each session contained to explicit indexing.
126+
125127
### Keeping Up to Date
126128

127129
```bash
@@ -466,6 +468,7 @@ See [docs/cbmignore.md](docs/cbmignore.md) for the full `.cbmignore` how-to: syn
466468
codebase-memory-mcp config list # show all settings
467469
codebase-memory-mcp config set auto_index true # auto-index on session start
468470
codebase-memory-mcp config set auto_index_limit 50000 # max files for auto-index
471+
codebase-memory-mcp config set auto_watch false # don't register background git watcher (default: true)
469472
codebase-memory-mcp config reset auto_index # reset to default
470473
```
471474

src/cli/cli.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2822,6 +2822,8 @@ int cbm_cmd_config(int argc, char **argv) {
28222822
"Enable auto-indexing on MCP session start");
28232823
printf(" %-25s default=%-10s %s\n", CBM_CONFIG_AUTO_INDEX_LIMIT, "50000",
28242824
"Max files for auto-indexing new projects");
2825+
printf(" %-25s default=%-10s %s\n", CBM_CONFIG_AUTO_WATCH, "true",
2826+
"Register background git watcher on session connect");
28252827
printf(" %-25s default=%-10s %s\n", CBM_CONFIG_UI_LANG, "auto",
28262828
"Pin graph UI language: en, zh, or auto");
28272829
return 0;
@@ -2849,6 +2851,8 @@ int cbm_cmd_config(int argc, char **argv) {
28492851
cbm_config_get(cfg, CBM_CONFIG_AUTO_INDEX, "false"));
28502852
printf(" %-25s = %-10s\n", CBM_CONFIG_AUTO_INDEX_LIMIT,
28512853
cbm_config_get(cfg, CBM_CONFIG_AUTO_INDEX_LIMIT, "50000"));
2854+
printf(" %-25s = %-10s\n", CBM_CONFIG_AUTO_WATCH,
2855+
cbm_config_get(cfg, CBM_CONFIG_AUTO_WATCH, "true"));
28522856
printf(" %-25s = %-10s\n", CBM_CONFIG_UI_LANG,
28532857
cbm_config_get(cfg, CBM_CONFIG_UI_LANG, "auto"));
28542858
} else if (strcmp(argv[0], "get") == 0) {

src/cli/cli.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ int cbm_config_delete(cbm_config_t *cfg, const char *key);
297297
/* Well-known config keys */
298298
#define CBM_CONFIG_AUTO_INDEX "auto_index"
299299
#define CBM_CONFIG_AUTO_INDEX_LIMIT "auto_index_limit"
300+
#define CBM_CONFIG_AUTO_WATCH "auto_watch"
300301
#define CBM_CONFIG_UI_LANG "ui-lang"
301302

302303
/* ── Subcommands (wired from main.c) ─────────────────────────── */

src/mcp/mcp.c

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5632,6 +5632,30 @@ static void detect_session(cbm_mcp_server_t *srv) {
56325632
}
56335633
}
56345634

5635+
/* auto_watch config: gates background watcher registration (default on).
5636+
* Multi-project users can contain a session to its own project with
5637+
* `config set auto_watch false`. */
5638+
static bool auto_watch_enabled(cbm_mcp_server_t *srv) {
5639+
if (!srv->config) {
5640+
return true; /* default on */
5641+
}
5642+
return cbm_config_get_bool(srv->config, CBM_CONFIG_AUTO_WATCH, true);
5643+
}
5644+
5645+
/* Register the session project with the background watcher for ongoing
5646+
* change detection — unless auto_watch is disabled. */
5647+
static void register_watcher_if_enabled(cbm_mcp_server_t *srv) {
5648+
if (!srv->watcher || srv->session_project[0] == '\0' || srv->session_root[0] == '\0') {
5649+
return;
5650+
}
5651+
if (!auto_watch_enabled(srv)) {
5652+
cbm_log_info("watcher.register.skipped", "reason", "auto_watch_off", "project",
5653+
srv->session_project);
5654+
return;
5655+
}
5656+
cbm_watcher_watch(srv->watcher, srv->session_project, srv->session_root);
5657+
}
5658+
56355659
/* Background auto-index thread function */
56365660
static void *autoindex_thread(void *arg) {
56375661
cbm_mcp_server_t *srv = (cbm_mcp_server_t *)arg;
@@ -5654,10 +5678,7 @@ static void *autoindex_thread(void *arg) {
56545678

56555679
if (rc == 0) {
56565680
cbm_log_info("autoindex.done", "project", srv->session_project);
5657-
/* Register with watcher for ongoing change detection */
5658-
if (srv->watcher) {
5659-
cbm_watcher_watch(srv->watcher, srv->session_project, srv->session_root);
5660-
}
5681+
register_watcher_if_enabled(srv);
56615682
} else {
56625683
cbm_log_warn("autoindex.err", "msg", "pipeline_run_failed");
56635684
}
@@ -5680,9 +5701,7 @@ static void maybe_auto_index(cbm_mcp_server_t *srv) {
56805701
/* Already indexed → register watcher for change detection */
56815702
cbm_log_info("autoindex.skip", "reason", "already_indexed", "project",
56825703
srv->session_project);
5683-
if (srv->watcher) {
5684-
cbm_watcher_watch(srv->watcher, srv->session_project, srv->session_root);
5685-
}
5704+
register_watcher_if_enabled(srv);
56865705
return;
56875706
}
56885707
}

tests/test_mcp.c

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
#include "../src/foundation/constants.h"
99
#include "../src/foundation/log.h"
1010
#include "test_framework.h"
11+
#include "test_helpers.h"
1112
#include <cli/cli.h>
1213
#include <mcp/mcp.h>
1314
#include <pipeline/pipeline.h>
1415
#include <store/store.h>
16+
#include <watcher/watcher.h>
1517
#include <yyjson/yyjson.h>
1618
#include <string.h>
1719
#include <stdlib.h>
@@ -4032,6 +4034,124 @@ TEST(readonly_query_succeeds_on_readonly_fs) {
40324034

40334035
#undef ROQ_PROJECT
40344036

4037+
/* ══════════════════════════════════════════════════════════════════
4038+
* AUTO_WATCH GATE (distilled from PR #625)
4039+
*
4040+
* Background watcher registration on session connect is gated by the
4041+
* `auto_watch` config key (default TRUE = existing behavior).
4042+
* ══════════════════════════════════════════════════════════════════ */
4043+
4044+
/* Drive the already-indexed connect path (initialize → maybe_auto_index →
4045+
* watcher registration) and return the resulting watch count.
4046+
* auto_watch_value: NULL leaves the key unset (exercises the default),
4047+
* otherwise the key is set to that value before initialize.
4048+
* Returns a negative code on fixture setup failure. */
4049+
static int auto_watch_connect_watch_count(const char *auto_watch_value) {
4050+
char cache[256];
4051+
snprintf(cache, sizeof(cache), "/tmp/cbm-autowatch-cache-XXXXXX");
4052+
if (!cbm_mkdtemp(cache)) {
4053+
return -1;
4054+
}
4055+
4056+
char repodir[512];
4057+
snprintf(repodir, sizeof(repodir), "%s/repo", cache);
4058+
if (th_mkdir_p(repodir) != 0) {
4059+
th_rmtree(cache);
4060+
return -2;
4061+
}
4062+
4063+
/* Same derivation detect_session uses on the cwd — realpath-based, so
4064+
* the name matches even where /tmp is a symlink (macOS). */
4065+
char *project = cbm_project_name_from_path(repodir);
4066+
if (!project) {
4067+
th_rmtree(cache);
4068+
return -3;
4069+
}
4070+
4071+
/* Pre-create <cache>/<project>.db so maybe_auto_index takes the
4072+
* "already indexed" branch — the watcher-registration site under test. */
4073+
char db_path[1024];
4074+
snprintf(db_path, sizeof(db_path), "%s/%s.db", cache, project);
4075+
if (th_write_file(db_path, "") != 0) {
4076+
free(project);
4077+
th_rmtree(cache);
4078+
return -4;
4079+
}
4080+
free(project);
4081+
4082+
const char *saved = getenv("CBM_CACHE_DIR");
4083+
char *saved_copy = saved ? strdup(saved) : NULL;
4084+
cbm_setenv("CBM_CACHE_DIR", cache, 1);
4085+
4086+
char old_cwd[1024];
4087+
if (!cbm_getcwd(old_cwd, sizeof(old_cwd)) || cbm_chdir(repodir) != 0) {
4088+
restore_cache_dir(saved_copy);
4089+
free(saved_copy);
4090+
th_rmtree(cache);
4091+
return -5;
4092+
}
4093+
4094+
int count = -6;
4095+
cbm_config_t *cfg = cbm_config_open(cache);
4096+
cbm_store_t *wstore = cbm_store_open_memory();
4097+
cbm_watcher_t *watcher = wstore ? cbm_watcher_new(wstore, NULL, NULL) : NULL;
4098+
if (cfg && watcher) {
4099+
if (auto_watch_value) {
4100+
cbm_config_set(cfg, CBM_CONFIG_AUTO_WATCH, auto_watch_value);
4101+
}
4102+
4103+
cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
4104+
if (srv) {
4105+
cbm_mcp_server_set_watcher(srv, watcher);
4106+
cbm_mcp_server_set_config(srv, cfg);
4107+
char *resp = cbm_mcp_server_handle(
4108+
srv, "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{}}");
4109+
free(resp);
4110+
count = cbm_watcher_watch_count(watcher);
4111+
cbm_mcp_server_free(srv);
4112+
}
4113+
}
4114+
4115+
if (watcher) {
4116+
cbm_watcher_free(watcher);
4117+
}
4118+
if (wstore) {
4119+
cbm_store_close(wstore);
4120+
}
4121+
if (cfg) {
4122+
cbm_config_close(cfg);
4123+
}
4124+
4125+
(void)cbm_chdir(old_cwd);
4126+
restore_cache_dir(saved_copy);
4127+
free(saved_copy);
4128+
th_rmtree(cache);
4129+
return count;
4130+
}
4131+
4132+
/* Default (key unset) → watcher registered on connect. Guards the
4133+
* no-behavior-change promise of the auto_watch gate: existing users keep
4134+
* background auto-sync without touching config. */
4135+
TEST(mcp_auto_watch_default_registers_watcher_on_connect) {
4136+
int count = auto_watch_connect_watch_count(NULL);
4137+
if (count < 0) {
4138+
PASS(); /* fixture setup failed (tmpdir/cwd unavailable) — skip */
4139+
}
4140+
ASSERT_EQ(count, 1);
4141+
PASS();
4142+
}
4143+
4144+
/* auto_watch=false → NO watcher registered on connect. RED on pre-gate code
4145+
* (registration was unconditional and the key did not exist). */
4146+
TEST(mcp_auto_watch_false_skips_watcher_on_connect) {
4147+
int count = auto_watch_connect_watch_count("false");
4148+
if (count < 0) {
4149+
PASS(); /* fixture setup failed (tmpdir/cwd unavailable) — skip */
4150+
}
4151+
ASSERT_EQ(count, 0);
4152+
PASS();
4153+
}
4154+
40354155
/* ══════════════════════════════════════════════════════════════════
40364156
* SUITE
40374157
* ══════════════════════════════════════════════════════════════════ */
@@ -4206,4 +4326,8 @@ SUITE(mcp) {
42064326
RUN_TEST(tool_bad_project_name_no_overflow_issue235);
42074327
RUN_TEST(tool_bad_project_error_valid_json_issue235);
42084328
RUN_TEST(tool_resolve_store_by_internal_name_issue704);
4329+
4330+
/* auto_watch gate (distilled from PR #625) */
4331+
RUN_TEST(mcp_auto_watch_default_registers_watcher_on_connect);
4332+
RUN_TEST(mcp_auto_watch_false_skips_watcher_on_connect);
42094333
}

0 commit comments

Comments
 (0)