mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-10-20 05:49:37 +08:00
feat(config): add watcher_enabled master switch for the background watcher (#335)
Add a persistent `watcher_enabled` config key (default true). When false, the daemon host skips building the background watcher entirely: the poll thread never starts and no projects are registered. Manual index_repository is unaffected, and the config wiring stays unconditional so auto_index / auto_watch keep working even when the watcher is off. This is distinct from auto_watch, which only gates per-session registration while the watcher IS running. watcher_enabled is the master switch that stops the subsystem from starting at all — the request in #335 (doc-heavy repos where every save churns the graph baseline). The key is read once, when the daemon builds its host state. Changing it takes effect when the daemon next starts; docs/CONFIGURATION.md says so explicitly. A disabled watcher is not a daemon-startup failure — the daemon still comes up and still owns IPC, HTTP/UI, project locks, and manual indexing; only the watcher and the in-memory store backing it go unbuilt. - cli.h/cli.c: CBM_CONFIG_WATCHER_ENABLED key + cbm_config_watcher_enabled() predicate (NULL-safe, default true), registered as a CONFIG_KEYS row next to auto_watch. The table is no longer only a print list: since #1522/#1558 it also backs key validation, so `config set/get/reset watcher_enabled` would be rejected as an unknown key without that row, and the default recorded there (true) is the one the table comment asks to keep in sync with the runtime reader's fallback. - daemon/host.c: gate watcher construction in host_state_prepare() and the thread create in host_background_start(); log watcher.disabled when skipped. Teardown needed no change — stop NULL-checks, join is gated on watcher_started, and cbm_watcher_free is NULL-safe. - test_cli.c: cli_config_watcher_enabled_default_and_persist pins the config layer — default on, disable/re-enable spellings, persistence across reopen, and a NULL-config default (a config-store failure must never silently disable the watcher). - docs: document watcher_enabled, and add the previously-undocumented auto_watch row to CONFIGURATION.md so the watcher knobs are described together; README example. Closes #335. Signed-off-by: soren <nguiasoren@gmail.com>
This commit is contained in:
@@ -667,6 +667,7 @@ codebase-memory-mcp config list # show all settings
|
||||
codebase-memory-mcp config set auto_index true # auto-index on session start
|
||||
codebase-memory-mcp config set auto_index_limit 50000 # max files for auto-index
|
||||
codebase-memory-mcp config set auto_watch false # don't register background git watcher (default: true)
|
||||
codebase-memory-mcp config set watcher_enabled false # stop the watcher thread entirely (default: true)
|
||||
codebase-memory-mcp config reset auto_index # reset to default
|
||||
```
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ codebase-memory-mcp config list
|
||||
codebase-memory-mcp config get auto_index
|
||||
codebase-memory-mcp config set auto_index true
|
||||
codebase-memory-mcp config set auto_index_limit 50000
|
||||
codebase-memory-mcp config set watcher_enabled false
|
||||
codebase-memory-mcp config reset auto_index
|
||||
```
|
||||
|
||||
@@ -86,6 +87,8 @@ Current keys:
|
||||
|---|---|---|
|
||||
| `auto_index` | `false` | Automatically index new projects when an MCP session starts. |
|
||||
| `auto_index_limit` | `50000` | Maximum file count allowed for automatic indexing of a new project. |
|
||||
| `auto_watch` | `true` | Register the session's project with the background git watcher on connect. Set `false` to keep a session from registering its project (the watcher still runs for other projects). |
|
||||
| `watcher_enabled` | `true` | Run the background watcher thread that auto-reindexes projects when they change. Set `false` to stop the watcher from starting at all — no poll thread and no project registration. Reindex manually with `index_repository` when disabled. |
|
||||
|
||||
## 3. UI Settings
|
||||
|
||||
|
||||
@@ -6734,6 +6734,14 @@ int cbm_config_delete(cbm_config_t *cfg, const char *key) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Whether the background watcher subsystem should run (default true). The
|
||||
* daemon host gates watcher construction and thread startup on this; see
|
||||
* cbm_config_watcher_enabled in cli.h. NULL-safe via cbm_config_get_bool (a
|
||||
* NULL cfg returns the default). */
|
||||
bool cbm_config_watcher_enabled(cbm_config_t *cfg) {
|
||||
return cbm_config_get_bool(cfg, CBM_CONFIG_WATCHER_ENABLED, true);
|
||||
}
|
||||
|
||||
/* ── Config CLI subcommand ────────────────────────────────────── */
|
||||
|
||||
/* THE config-key table. list, get, help, and key validation all read this one
|
||||
@@ -6753,6 +6761,8 @@ static const config_key_def_t CONFIG_KEYS[] = {
|
||||
{CBM_CONFIG_AUTO_INDEX, "false", "Enable auto-indexing on MCP session start"},
|
||||
{CBM_CONFIG_AUTO_INDEX_LIMIT, "50000", "Max files for auto-indexing new projects"},
|
||||
{CBM_CONFIG_AUTO_WATCH, "true", "Register background git watcher on session connect"},
|
||||
{CBM_CONFIG_WATCHER_ENABLED, "true",
|
||||
"Run the background watcher thread (auto-reindex); false to disable"},
|
||||
{CBM_CONFIG_UI_LANG, "auto", "Pin graph UI language: en, zh, or auto"},
|
||||
{CBM_CONFIG_UI_ENABLED, "false", "Serve the graph UI on a loopback HTTP port"},
|
||||
{CBM_CONFIG_UI_PORT, "9749", "Port for the graph UI listener when enabled"},
|
||||
|
||||
@@ -422,11 +422,21 @@ int cbm_config_delete(cbm_config_t *cfg, const char *key);
|
||||
#define CBM_CONFIG_AUTO_INDEX_LIMIT "auto_index_limit"
|
||||
#define CBM_CONFIG_AUTO_WATCH "auto_watch"
|
||||
#define CBM_CONFIG_UI_LANG "ui-lang"
|
||||
#define CBM_CONFIG_WATCHER_ENABLED "watcher_enabled"
|
||||
/* #1558: the graph UI's loopback listener. Stored in the UI config file rather
|
||||
* than the key-value store, but surfaced through `config` so it is findable. */
|
||||
#define CBM_CONFIG_UI_ENABLED "ui_enabled"
|
||||
#define CBM_CONFIG_UI_PORT "ui_port"
|
||||
|
||||
/* Whether the background watcher subsystem should run at all (default true).
|
||||
* When false, the daemon host skips building and starting the watcher entirely:
|
||||
* the poll thread never starts and no projects are registered (#335). Read once
|
||||
* at daemon startup. Distinct from auto_watch, which only gates per-session
|
||||
* registration while the watcher IS running. NULL-safe — a NULL cfg returns the
|
||||
* default (true), so a failure to open the config store never silently disables
|
||||
* the watcher. */
|
||||
bool cbm_config_watcher_enabled(cbm_config_t *cfg);
|
||||
|
||||
/* ── Binary activation safety ─────────────────────────────────── */
|
||||
|
||||
typedef void *cbm_cli_activation_lock_t;
|
||||
|
||||
+28
-6
@@ -583,9 +583,22 @@ static bool host_state_prepare(host_state_t *host, const cbm_daemon_ipc_endpoint
|
||||
cbm_log_error("daemon.runtime_config_open_failed", "reason", "config_db_unavailable");
|
||||
return false;
|
||||
}
|
||||
host->watch_store = cbm_store_open_memory();
|
||||
host->project_locks = cbm_project_lock_manager_new(endpoint);
|
||||
host->watcher = cbm_watcher_new(host->watch_store, host_watcher_index, host);
|
||||
/* #335: watcher_enabled (default true) is the master switch for the
|
||||
* background watcher. When false the daemon never builds the watcher or the
|
||||
* in-memory store backing it, so the poll thread never starts
|
||||
* (host_background_start has nothing to run) and no project ever registers
|
||||
* (register_watcher_if_enabled early-returns on a NULL watcher). The daemon
|
||||
* still starts and still owns everything else: IPC, HTTP/UI, project locks,
|
||||
* and manual index_repository. Read once here at daemon startup, so a change
|
||||
* takes effect when the daemon next starts — see docs/CONFIGURATION.md. */
|
||||
bool watcher_enabled = cbm_config_watcher_enabled(host->runtime_config);
|
||||
if (watcher_enabled) {
|
||||
host->watch_store = cbm_store_open_memory();
|
||||
host->watcher = cbm_watcher_new(host->watch_store, host_watcher_index, host);
|
||||
} else {
|
||||
cbm_log_info("watcher.disabled", "reason", "config");
|
||||
}
|
||||
cbm_daemon_application_config_t application_config = {
|
||||
.watcher = host->watcher,
|
||||
.config = host->runtime_config,
|
||||
@@ -598,7 +611,12 @@ static bool host_state_prepare(host_state_t *host, const cbm_daemon_ipc_endpoint
|
||||
if (host->application && host->permanent) {
|
||||
cbm_daemon_application_set_permanent(host->application, true);
|
||||
}
|
||||
if (!host->watch_store || !host->watcher || !host->project_locks || !host->application) {
|
||||
if (!host->project_locks || !host->application) {
|
||||
return false;
|
||||
}
|
||||
/* A watcher deliberately left unbuilt by watcher_enabled=false is not a
|
||||
* startup failure; an allocation failure while it is enabled still is. */
|
||||
if (watcher_enabled && (!host->watch_store || !host->watcher)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -824,10 +842,14 @@ bool cbm_daemon_host_http_thread_create_failure_lifecycle_for_test(void) {
|
||||
}
|
||||
|
||||
static bool host_background_start(host_state_t *host) {
|
||||
if (cbm_thread_create(&host->watcher_thread, 0, host_watcher_thread, host->watcher) != 0) {
|
||||
return false;
|
||||
/* No watcher object when watcher_enabled=false (#335) — nothing to run, and
|
||||
* the daemon must still come up with its remaining subsystems. */
|
||||
if (host->watcher) {
|
||||
if (cbm_thread_create(&host->watcher_thread, 0, host_watcher_thread, host->watcher) != 0) {
|
||||
return false;
|
||||
}
|
||||
host->watcher_started = true;
|
||||
}
|
||||
host->watcher_started = true;
|
||||
|
||||
host_http_reconcile_at(host, cbm_now_ms(), true);
|
||||
return true;
|
||||
|
||||
@@ -12291,6 +12291,55 @@ TEST(cli_config_persists) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(cli_config_watcher_enabled_default_and_persist) {
|
||||
/* #335: the background watcher is on by default and can be disabled via the
|
||||
* persisted `watcher_enabled` key. This pins the config layer only — default
|
||||
* preservation, the accepted spellings, and persistence across reopen. That
|
||||
* the daemon host actually honours the key (watcher never built, thread
|
||||
* never started, no registration) is proven separately by the process-level
|
||||
* regression in tests/test_watcher_disabled.sh, which fails if the gate in
|
||||
* host_state_prepare() is removed. */
|
||||
char tmpdir[256];
|
||||
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-cfg-XXXXXX");
|
||||
if (!cbm_mkdtemp(tmpdir))
|
||||
FAIL("cbm_mkdtemp failed");
|
||||
|
||||
/* A NULL config (store failed to open) defaults to ON — a config failure
|
||||
* must never silently disable the watcher. */
|
||||
ASSERT_TRUE(cbm_config_watcher_enabled(NULL));
|
||||
|
||||
cbm_config_t *cfg = cbm_config_open(tmpdir);
|
||||
ASSERT_NOT_NULL(cfg);
|
||||
|
||||
/* Default: absent key → watcher runs. */
|
||||
ASSERT_TRUE(cbm_config_watcher_enabled(cfg));
|
||||
|
||||
/* Disable: false / 0 / off all turn the watcher off. */
|
||||
cbm_config_set(cfg, CBM_CONFIG_WATCHER_ENABLED, "false");
|
||||
ASSERT_FALSE(cbm_config_watcher_enabled(cfg));
|
||||
cbm_config_set(cfg, CBM_CONFIG_WATCHER_ENABLED, "0");
|
||||
ASSERT_FALSE(cbm_config_watcher_enabled(cfg));
|
||||
cbm_config_set(cfg, CBM_CONFIG_WATCHER_ENABLED, "off");
|
||||
ASSERT_FALSE(cbm_config_watcher_enabled(cfg));
|
||||
|
||||
/* Re-enable: true / 1 / on all turn it back on. */
|
||||
cbm_config_set(cfg, CBM_CONFIG_WATCHER_ENABLED, "on");
|
||||
ASSERT_TRUE(cbm_config_watcher_enabled(cfg));
|
||||
cbm_config_set(cfg, CBM_CONFIG_WATCHER_ENABLED, "1");
|
||||
ASSERT_TRUE(cbm_config_watcher_enabled(cfg));
|
||||
|
||||
/* Persistence: the disabled state survives close + reopen. */
|
||||
cbm_config_set(cfg, CBM_CONFIG_WATCHER_ENABLED, "false");
|
||||
cbm_config_close(cfg);
|
||||
cfg = cbm_config_open(tmpdir);
|
||||
ASSERT_NOT_NULL(cfg);
|
||||
ASSERT_FALSE(cbm_config_watcher_enabled(cfg));
|
||||
|
||||
cbm_config_close(cfg);
|
||||
test_rmdir_r(tmpdir);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
* Group H: cbm_replace_binary (update command helper)
|
||||
* ═══════════════════════════════════════════════════════════════════ */
|
||||
@@ -13301,6 +13350,7 @@ SUITE(cli) {
|
||||
RUN_TEST(cli_config_get_int);
|
||||
RUN_TEST(cli_config_delete);
|
||||
RUN_TEST(cli_config_persists);
|
||||
RUN_TEST(cli_config_watcher_enabled_default_and_persist);
|
||||
|
||||
/* Replace binary (update command helper — group H) */
|
||||
#ifndef _WIN32
|
||||
|
||||
Reference in New Issue
Block a user