Skip to content

Commit 7629940

Browse files
committed
Restore session auto-detect + auto-index + config CLI from Go
- Config store: persistent SQLite key-value in _config.db (get/set/delete/bool/int) - Config CLI: codebase-memory-mcp config list|get|set|reset Keys: auto_index (default false), auto_index_limit (default 50000) - Session detection: detect project root from CWD on MCP initialize - Auto-index: if auto_index=true and project not yet indexed, background pipeline - Watcher hookup: after index completes, register project for ongoing git polling - 6 new config store tests (open/close, get/set, bool, int, delete, persistence) Enable auto-indexing: codebase-memory-mcp config set auto_index true
1 parent 0b8a456 commit 7629940

File tree

6 files changed

+592
-0
lines changed

6 files changed

+592
-0
lines changed

src/cli/cli.c

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,6 +1677,225 @@ int cbm_remove_indexes(const char *home_dir) {
16771677
return count;
16781678
}
16791679

1680+
/* ── Config store (persistent key-value in _config.db) ─────────── */
1681+
1682+
#include <sqlite3.h>
1683+
1684+
struct cbm_config {
1685+
sqlite3 *db;
1686+
char get_buf[4096]; /* static buffer for cbm_config_get return values */
1687+
};
1688+
1689+
cbm_config_t *cbm_config_open(const char *cache_dir) {
1690+
if (!cache_dir) {
1691+
return NULL;
1692+
}
1693+
1694+
char dbpath[1024];
1695+
snprintf(dbpath, sizeof(dbpath), "%s/_config.db", cache_dir);
1696+
1697+
/* Ensure directory exists */
1698+
mkdirp(cache_dir, DIR_PERMS);
1699+
1700+
sqlite3 *db = NULL;
1701+
if (sqlite3_open(dbpath, &db) != SQLITE_OK) {
1702+
if (db) {
1703+
sqlite3_close(db);
1704+
}
1705+
return NULL;
1706+
}
1707+
1708+
/* Create table if not exists */
1709+
const char *sql = "CREATE TABLE IF NOT EXISTS config (key TEXT PRIMARY KEY, value TEXT)";
1710+
char *err_msg = NULL;
1711+
if (sqlite3_exec(db, sql, NULL, NULL, &err_msg) != SQLITE_OK) {
1712+
sqlite3_free(err_msg);
1713+
sqlite3_close(db);
1714+
return NULL;
1715+
}
1716+
1717+
cbm_config_t *cfg = calloc(1, sizeof(*cfg));
1718+
if (!cfg) {
1719+
sqlite3_close(db);
1720+
return NULL;
1721+
}
1722+
cfg->db = db;
1723+
return cfg;
1724+
}
1725+
1726+
void cbm_config_close(cbm_config_t *cfg) {
1727+
if (!cfg) {
1728+
return;
1729+
}
1730+
if (cfg->db) {
1731+
sqlite3_close(cfg->db);
1732+
}
1733+
free(cfg);
1734+
}
1735+
1736+
const char *cbm_config_get(cbm_config_t *cfg, const char *key, const char *default_val) {
1737+
if (!cfg || !key) {
1738+
return default_val;
1739+
}
1740+
1741+
sqlite3_stmt *stmt = NULL;
1742+
if (sqlite3_prepare_v2(cfg->db, "SELECT value FROM config WHERE key = ?", -1, &stmt, NULL) !=
1743+
SQLITE_OK) {
1744+
return default_val;
1745+
}
1746+
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
1747+
1748+
const char *result = default_val;
1749+
if (sqlite3_step(stmt) == SQLITE_ROW) {
1750+
const char *val = (const char *)sqlite3_column_text(stmt, 0);
1751+
if (val) {
1752+
snprintf(cfg->get_buf, sizeof(cfg->get_buf), "%s", val);
1753+
result = cfg->get_buf;
1754+
}
1755+
}
1756+
sqlite3_finalize(stmt);
1757+
return result;
1758+
}
1759+
1760+
bool cbm_config_get_bool(cbm_config_t *cfg, const char *key, bool default_val) {
1761+
const char *val = cbm_config_get(cfg, key, NULL);
1762+
if (!val) {
1763+
return default_val;
1764+
}
1765+
if (strcmp(val, "true") == 0 || strcmp(val, "1") == 0 || strcmp(val, "on") == 0) {
1766+
return true;
1767+
}
1768+
if (strcmp(val, "false") == 0 || strcmp(val, "0") == 0 || strcmp(val, "off") == 0) {
1769+
return false;
1770+
}
1771+
return default_val;
1772+
}
1773+
1774+
int cbm_config_get_int(cbm_config_t *cfg, const char *key, int default_val) {
1775+
const char *val = cbm_config_get(cfg, key, NULL);
1776+
if (!val) {
1777+
return default_val;
1778+
}
1779+
char *endptr;
1780+
long v = strtol(val, &endptr, 10);
1781+
if (endptr == val || *endptr != '\0') {
1782+
return default_val;
1783+
}
1784+
return (int)v;
1785+
}
1786+
1787+
int cbm_config_set(cbm_config_t *cfg, const char *key, const char *value) {
1788+
if (!cfg || !key || !value) {
1789+
return -1;
1790+
}
1791+
1792+
sqlite3_stmt *stmt = NULL;
1793+
if (sqlite3_prepare_v2(cfg->db, "INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)", -1,
1794+
&stmt, NULL) != SQLITE_OK) {
1795+
return -1;
1796+
}
1797+
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
1798+
sqlite3_bind_text(stmt, 2, value, -1, SQLITE_TRANSIENT);
1799+
1800+
int rc = sqlite3_step(stmt) == SQLITE_DONE ? 0 : -1;
1801+
sqlite3_finalize(stmt);
1802+
return rc;
1803+
}
1804+
1805+
int cbm_config_delete(cbm_config_t *cfg, const char *key) {
1806+
if (!cfg || !key) {
1807+
return -1;
1808+
}
1809+
1810+
sqlite3_stmt *stmt = NULL;
1811+
if (sqlite3_prepare_v2(cfg->db, "DELETE FROM config WHERE key = ?", -1, &stmt, NULL) !=
1812+
SQLITE_OK) {
1813+
return -1;
1814+
}
1815+
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
1816+
1817+
int rc = sqlite3_step(stmt) == SQLITE_DONE ? 0 : -1;
1818+
sqlite3_finalize(stmt);
1819+
return rc;
1820+
}
1821+
1822+
/* ── Config CLI subcommand ────────────────────────────────────── */
1823+
1824+
int cbm_cmd_config(int argc, char **argv) {
1825+
if (argc == 0) {
1826+
printf("Usage: codebase-memory-mcp config <command> [args]\n\n");
1827+
printf("Commands:\n");
1828+
printf(" list Show all config values\n");
1829+
printf(" get <key> Get a config value\n");
1830+
printf(" set <key> <val> Set a config value\n");
1831+
printf(" reset <key> Reset a key to default\n\n");
1832+
printf("Config keys:\n");
1833+
printf(" %-25s default=%-10s %s\n", CBM_CONFIG_AUTO_INDEX, "false",
1834+
"Enable auto-indexing on MCP session start");
1835+
printf(" %-25s default=%-10s %s\n", CBM_CONFIG_AUTO_INDEX_LIMIT, "50000",
1836+
"Max files for auto-indexing new projects");
1837+
return 0;
1838+
}
1839+
1840+
// NOLINTNEXTLINE(concurrency-mt-unsafe)
1841+
const char *home = getenv("HOME");
1842+
if (!home) {
1843+
fprintf(stderr, "error: HOME not set\n");
1844+
return 1;
1845+
}
1846+
1847+
char cache_dir[1024];
1848+
snprintf(cache_dir, sizeof(cache_dir), "%s/.cache/codebase-memory-mcp", home);
1849+
1850+
cbm_config_t *cfg = cbm_config_open(cache_dir);
1851+
if (!cfg) {
1852+
fprintf(stderr, "error: cannot open config database\n");
1853+
return 1;
1854+
}
1855+
1856+
int rc = 0;
1857+
if (strcmp(argv[0], "list") == 0 || strcmp(argv[0], "ls") == 0) {
1858+
printf("Configuration:\n");
1859+
printf(" %-25s = %-10s\n", CBM_CONFIG_AUTO_INDEX,
1860+
cbm_config_get(cfg, CBM_CONFIG_AUTO_INDEX, "false"));
1861+
printf(" %-25s = %-10s\n", CBM_CONFIG_AUTO_INDEX_LIMIT,
1862+
cbm_config_get(cfg, CBM_CONFIG_AUTO_INDEX_LIMIT, "50000"));
1863+
} else if (strcmp(argv[0], "get") == 0) {
1864+
if (argc < 2) {
1865+
fprintf(stderr, "Usage: config get <key>\n");
1866+
rc = 1;
1867+
} else {
1868+
printf("%s\n", cbm_config_get(cfg, argv[1], ""));
1869+
}
1870+
} else if (strcmp(argv[0], "set") == 0) {
1871+
if (argc < 3) {
1872+
fprintf(stderr, "Usage: config set <key> <value>\n");
1873+
rc = 1;
1874+
} else {
1875+
if (cbm_config_set(cfg, argv[1], argv[2]) == 0) {
1876+
printf("%s = %s\n", argv[1], argv[2]);
1877+
} else {
1878+
fprintf(stderr, "error: failed to set %s\n", argv[1]);
1879+
rc = 1;
1880+
}
1881+
}
1882+
} else if (strcmp(argv[0], "reset") == 0) {
1883+
if (argc < 2) {
1884+
fprintf(stderr, "Usage: config reset <key>\n");
1885+
rc = 1;
1886+
} else {
1887+
cbm_config_delete(cfg, argv[1]);
1888+
printf("%s reset to default\n", argv[1]);
1889+
}
1890+
} else {
1891+
fprintf(stderr, "Unknown config command: %s\n", argv[0]);
1892+
rc = 1;
1893+
}
1894+
1895+
cbm_config_close(cfg);
1896+
return rc;
1897+
}
1898+
16801899
/* ── Interactive prompt ───────────────────────────────────────── */
16811900

16821901
/* Global auto-answer mode: 0=interactive, 1=always yes, -1=always no */

src/cli/cli.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,36 @@ int cbm_list_indexes(const char *home_dir);
201201
/* Remove all .db files in the cache directory. Returns count removed. */
202202
int cbm_remove_indexes(const char *home_dir);
203203

204+
/* ── Config store (persistent key-value, backed by _config.db) ── */
205+
206+
typedef struct cbm_config cbm_config_t;
207+
208+
/* Open the config store in the given cache directory.
209+
* Creates _config.db if it doesn't exist. Returns NULL on error. */
210+
cbm_config_t *cbm_config_open(const char *cache_dir);
211+
212+
/* Close the config store. */
213+
void cbm_config_close(cbm_config_t *cfg);
214+
215+
/* Get a config value. Returns default_val if key not found. */
216+
const char *cbm_config_get(cbm_config_t *cfg, const char *key, const char *default_val);
217+
218+
/* Get a config value as bool. "true"/"1"/"on" → true. */
219+
bool cbm_config_get_bool(cbm_config_t *cfg, const char *key, bool default_val);
220+
221+
/* Get a config value as int. Returns default_val if not found or invalid. */
222+
int cbm_config_get_int(cbm_config_t *cfg, const char *key, int default_val);
223+
224+
/* Set a config value. Returns 0 on success. */
225+
int cbm_config_set(cbm_config_t *cfg, const char *key, const char *value);
226+
227+
/* Delete a config key. Returns 0 on success. */
228+
int cbm_config_delete(cbm_config_t *cfg, const char *key);
229+
230+
/* Well-known config keys */
231+
#define CBM_CONFIG_AUTO_INDEX "auto_index"
232+
#define CBM_CONFIG_AUTO_INDEX_LIMIT "auto_index_limit"
233+
204234
/* ── Subcommands (wired from main.c) ─────────────────────────── */
205235

206236
/* install: copy binary, install skills, install editor MCP configs, ensure PATH.
@@ -214,4 +244,7 @@ int cbm_cmd_uninstall(int argc, char **argv);
214244
* download and replace binary. */
215245
int cbm_cmd_update(int argc, char **argv);
216246

247+
/* config: get/set/list/reset runtime config values. */
248+
int cbm_cmd_config(int argc, char **argv);
249+
217250
#endif /* CBM_CLI_H */

src/main.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ static void print_help(void) {
127127
printf(" codebase-memory-mcp install [-y|-n] [--force] [--dry-run]\n");
128128
printf(" codebase-memory-mcp uninstall [-y|-n] [--dry-run]\n");
129129
printf(" codebase-memory-mcp update [-y|-n]\n");
130+
printf(" codebase-memory-mcp config <list|get|set|reset>\n");
130131
printf(" codebase-memory-mcp --version Print version\n");
131132
printf(" codebase-memory-mcp --help Print this help\n");
132133
printf("\nUI options:\n");
@@ -167,6 +168,9 @@ int main(int argc, char **argv) {
167168
if (strcmp(argv[i], "update") == 0) {
168169
return cbm_cmd_update(argc - i - 1, argv + i + 1);
169170
}
171+
if (strcmp(argv[i], "config") == 0) {
172+
return cbm_cmd_config(argc - i - 1, argv + i + 1);
173+
}
170174
}
171175

172176
/* Default: MCP server on stdio */
@@ -215,16 +219,30 @@ int main(int argc, char **argv) {
215219
sigaction(SIGINT, &sa, NULL);
216220
#endif
217221

222+
/* Open config store for runtime settings */
223+
char config_dir[1024];
224+
const char *cfg_home = getenv("HOME");
225+
cbm_config_t *runtime_config = NULL;
226+
if (cfg_home) {
227+
snprintf(config_dir, sizeof(config_dir), "%s/.cache/codebase-memory-mcp", cfg_home);
228+
runtime_config = cbm_config_open(config_dir);
229+
}
230+
218231
/* Create MCP server */
219232
g_server = cbm_mcp_server_new(NULL);
220233
if (!g_server) {
221234
cbm_log_error("server.err", "msg", "failed to create server");
235+
cbm_config_close(runtime_config);
222236
return 1;
223237
}
224238

225239
/* Create and start watcher in background thread */
226240
cbm_store_t *watch_store = cbm_store_open_memory();
227241
g_watcher = cbm_watcher_new(watch_store, watcher_index_fn, NULL);
242+
243+
/* Wire watcher + config into MCP server for session auto-index */
244+
cbm_mcp_server_set_watcher(g_server, g_watcher);
245+
cbm_mcp_server_set_config(g_server, runtime_config);
228246
cbm_thread_t watcher_tid;
229247
bool watcher_started = false;
230248

@@ -269,6 +287,7 @@ int main(int argc, char **argv) {
269287
cbm_watcher_free(g_watcher);
270288
cbm_store_close(watch_store);
271289
cbm_mcp_server_free(g_server);
290+
cbm_config_close(runtime_config);
272291

273292
g_watcher = NULL;
274293
g_server = NULL;

0 commit comments

Comments
 (0)