From 49b74abc4b2795bd008ae99f20e3559735a11eba Mon Sep 17 00:00:00 2001 From: Alejandro Blanco-M Date: Sat, 4 Jul 2026 08:47:37 +0200 Subject: [PATCH 1/5] feat: add CLI skill mode and MCP idle timeout --- src/cli/cli.c | 103 ++++++++++++++++++++++++++++++++++++++++++++--- src/cli/cli.h | 3 ++ src/main.c | 35 +++++++++++++++- src/mcp/mcp.c | 26 +++++++++--- src/mcp/mcp.h | 8 ++++ tests/test_cli.c | 17 +++++++- tests/test_mcp.c | 28 +++++++++++++ 7 files changed, 206 insertions(+), 14 deletions(-) diff --git a/src/cli/cli.c b/src/cli/cli.c index aec3cf102..1e66888d2 100644 --- a/src/cli/cli.c +++ b/src/cli/cli.c @@ -489,7 +489,7 @@ int cbm_replace_binary(const char *path, const unsigned char *data, int len, int /* Consolidated from 4 separate skills into 1 with progressive disclosure. * This embedded version is the single source of truth for the CLI installer. * Based on PR #81 by @gdilla — factual corrections applied. */ -static const char skill_content[] = +static const char mcp_skill_content[] = "---\n" "name: codebase-memory\n" "description: Use the codebase knowledge graph for structural code queries. " @@ -598,6 +598,66 @@ static const char skill_content[] = "`direction=\"both\"`.\n" "5. `search_graph` results default to 50 per page — check `has_more` and use `offset`.\n"; +static const char cli_skill_content[] = + "---\n" + "name: codebase-memory\n" + "description: Use the codebase-memory-mcp binary through bash CLI commands for " + "codebase exploration, architecture, tracing, quality, and graph queries. CLI mode is " + "the only mechanism for this skill; do not use MCP tools unless the user explicitly asks.\n" + "---\n" + "\n" + "# Codebase Memory — CLI Skill\n" + "\n" + "Use `codebase-memory-mcp` as a normal executable. Do not rely on a permanent MCP " + "connection. Run each request through bash:\n" + "\n" + "```bash\n" + "codebase-memory-mcp cli ''\n" + "```\n" + "\n" + "## First checks\n" + "```bash\n" + "command -v codebase-memory-mcp\n" + "codebase-memory-mcp --version\n" + "codebase-memory-mcp cli list_projects '{}'\n" + "```\n" + "\n" + "If binary is missing, install binary-only, not MCP config:\n" + "```bash\n" + "curl -fsSL https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.sh | bash -s -- --skip-config\n" + "```\n" + "\n" + "## Project workflow\n" + "1. `list_projects` through CLI.\n" + "2. If current repo is absent, run `index_repository`.\n" + "3. Use `search_graph` before reading files.\n" + "4. Use `get_code_snippet` for exact source after finding `qualified_name`.\n" + "\n" + "Safe JSON pattern:\n" + "```bash\n" + "project=PROJECT\n" + "json=$(jq -nc --arg project \"$project\" --arg q \"auth handler\" '{project:$project,query:$q,limit:20}')\n" + "codebase-memory-mcp cli search_graph \"$json\"\n" + "```\n" + "\n" + "## Common commands\n" + "```bash\n" + "codebase-memory-mcp cli index_repository '{\"repo_path\":\"/path/to/repo\",\"name\":\"my-project\",\"mode\":\"moderate\"}'\n" + "codebase-memory-mcp cli search_graph '{\"project\":\"PROJECT\",\"query\":\"update settings\",\"limit\":20}'\n" + "codebase-memory-mcp cli search_graph '{\"project\":\"PROJECT\",\"label\":\"Route\",\"limit\":100}'\n" + "codebase-memory-mcp cli get_code_snippet '{\"project\":\"PROJECT\",\"qualified_name\":\"pkg/orders.ProcessOrder\",\"include_neighbors\":true}'\n" + "codebase-memory-mcp cli trace_path '{\"project\":\"PROJECT\",\"function_name\":\"ProcessOrder\",\"direction\":\"both\",\"depth\":3}'\n" + "codebase-memory-mcp cli get_architecture '{\"project\":\"PROJECT\",\"aspects\":[\"packages\",\"dependencies\",\"clusters\"]}'\n" + "codebase-memory-mcp cli query_graph '{\"project\":\"PROJECT\",\"query\":\"MATCH (f:Function) RETURN f.qualified_name LIMIT 20\",\"max_rows\":20}'\n" + "```\n" + "\n" + "## Rules\n" + "- CLI first and only for this skill.\n" + "- Use `jq` for JSON creation and summarizing large outputs.\n" + "- Save large raw outputs to `/tmp/cbm-*.json`.\n" + "- Fall back to file search only for non-indexed files or exact literals missing from graph.\n" + "- Ask before `delete_project`, `uninstall`, or global config mutation.\n"; + static const char codex_instructions_content[] = "# Codebase Knowledge Graph\n" "\n" @@ -621,10 +681,17 @@ static const char *old_skill_names[] = { }; enum { OLD_SKILL_COUNT = 4 }; -static const cbm_skill_t skills[CBM_SKILL_COUNT] = { - {"codebase-memory", skill_content}, +static bool g_cli_skill_mode = false; + +static cbm_skill_t skills[CBM_SKILL_COUNT] = { + {"codebase-memory", mcp_skill_content}, }; +void cbm_set_cli_skill_mode(bool enabled) { + g_cli_skill_mode = enabled; + skills[0].content = enabled ? cli_skill_content : mcp_skill_content; +} + const cbm_skill_t *cbm_get_skills(void) { return skills; } @@ -5650,6 +5717,9 @@ static void install_claude_code_config(const char *home, const char *binary_path .dialect = CBM_GRAPH_DIALECT_CLAUDE, }, dry_run); + if (g_cli_skill_mode) { + return; + } snprintf(p, sizeof(p), "%s/.claude.json", user_root); plan_record("Claude Code", "mcp_config", p); snprintf(p, sizeof(p), "%s/settings.json", config_dir); @@ -5681,6 +5751,11 @@ static void install_claude_code_config(const char *home, const char *binary_path printf(" removed old monolithic skill\n"); } + if (g_cli_skill_mode) { + printf(" skill-mode: cli (MCP configs/hooks skipped)\n"); + return; + } + /* ~/.claude/.mcp.json is not a documented Claude Code MCP location. * Remove only our legacy entry there instead of perpetuating that file. */ char legacy_mcp_path[CLI_BUF_1K]; @@ -7317,6 +7392,12 @@ int cbm_install_agent_configs(const char *home, const char *binary_path, bool fo if (agents.claude_code) { install_claude_code_config(home, binary_path, force, dry_run); } + if (g_cli_skill_mode) { + if (!agents.claude_code && !g_install_plan) { + printf("skill-mode: cli requested, but no Claude Code skills directory was detected.\n"); + } + return; + } install_cli_agent_configs(&agents, home, binary_path, force, dry_run); install_editor_agent_configs(&agents, home, binary_path, force, dry_run); install_additional_agent_configs(&agents, home, binary_path, force, dry_run); @@ -7481,6 +7562,7 @@ char *cbm_build_install_plan_json(const char *home, const char *binary_path) { yyjson_mut_val *root = yyjson_mut_obj(doc); yyjson_mut_doc_set_root(doc, root); yyjson_mut_obj_add_str(doc, root, "type", "agent.install.plan.v1"); + yyjson_mut_obj_add_str(doc, root, "skill_mode", g_cli_skill_mode ? "cli" : "mcp"); yyjson_mut_val *agents = yyjson_mut_arr(doc); for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) { @@ -7534,7 +7616,9 @@ char *cbm_build_install_plan_json(const char *home, const char *binary_path) { yyjson_mut_obj_add_val(doc, root, "hooks_planned", hooks); yyjson_mut_obj_add_bool(doc, root, "writes_started", false); yyjson_mut_obj_add_bool(doc, root, "network_after_install", false); - yyjson_mut_obj_add_str(doc, root, "next_safe_command", "codebase-memory-mcp install -y"); + yyjson_mut_obj_add_str(doc, root, "next_safe_command", + g_cli_skill_mode ? "codebase-memory-mcp install -y --skill-mode=cli" + : "codebase-memory-mcp install -y --skill-mode=mcp"); char *json = yyjson_mut_write(doc, YYJSON_WRITE_PRETTY, NULL); yyjson_mut_doc_free(doc); @@ -7548,6 +7632,7 @@ int cbm_cmd_install(int argc, char **argv) { bool force = false; bool plan = false; bool reset_indexes = false; + bool cli_skill_mode = false; for (int i = 0; i < argc; i++) { if (strcmp(argv[i], "--dry-run") == 0) { dry_run = true; @@ -7563,7 +7648,14 @@ int cbm_cmd_install(int argc, char **argv) { if (strcmp(argv[i], "--reset-indexes") == 0) { reset_indexes = true; } + if (strcmp(argv[i], "--skill-mode=cli") == 0 || strcmp(argv[i], "--cli-skill") == 0) { + cli_skill_mode = true; + } + if (strcmp(argv[i], "--skill-mode=mcp") == 0) { + cli_skill_mode = false; + } } + cbm_set_cli_skill_mode(cli_skill_mode); const char *home = cbm_get_home_dir(); if (!home) { @@ -7587,7 +7679,8 @@ int cbm_cmd_install(int argc, char **argv) { return 0; } - printf("codebase-memory-mcp install %s\n\n", CBM_VERSION); + printf("codebase-memory-mcp install %s\n", CBM_VERSION); + printf("skill-mode: %s\n\n", cli_skill_mode ? "cli" : "mcp"); /* (#607) Default: preserve existing indexes. `--reset-indexes` opts into * the old prompt-and-delete behaviour. The helper returns 0 only when the diff --git a/src/cli/cli.h b/src/cli/cli.h index f45325313..3b3e1e769 100644 --- a/src/cli/cli.h +++ b/src/cli/cli.h @@ -87,6 +87,9 @@ typedef struct { /* Get the array of skill definitions. */ const cbm_skill_t *cbm_get_skills(void); +/* Select installed skill content. false = MCP tool skill (default), true = CLI-only skill. */ +void cbm_set_cli_skill_mode(bool enabled); + /* Install skills to skills_dir (e.g. ~/.claude/skills/). * If force is true, overwrite existing skills. * Returns count of skills written. */ diff --git a/src/main.c b/src/main.c index 01083e809..ecf381afa 100644 --- a/src/main.c +++ b/src/main.c @@ -9,6 +9,7 @@ * --ui=true/false Enable/disable HTTP UI server (persisted) * --port=N Set HTTP UI port (persisted, default 9749) * --tool-profile=analysis|scout Expose a restricted agent tool surface + * --idle-timeout=N Exit after N seconds without MCP requests (default: disabled) * * Signal handling: SIGTERM/SIGINT trigger graceful shutdown. * Watcher runs in a background thread, polling for git changes. @@ -29,6 +30,7 @@ enum { MAIN_CLI_ARGC = 2, MAIN_FLAG_OFF = 5, /* strlen("--ui=") */ MAIN_PORT_OFF = 7, /* strlen("--port=") */ + MAIN_IDLE_TIMEOUT_OFF = 15, /* strlen("--idle-timeout=") */ MAIN_MAX_PORT = 65536, PARENT_WATCHDOG_STACK_SIZE = 64 * CBM_SZ_1K, /* watchdog only polls — tiny stack suffices */ }; @@ -510,7 +512,7 @@ static void print_help(void) { printf("Usage:\n"); printf(" codebase-memory-mcp Run MCP server on stdio\n"); printf(" codebase-memory-mcp cli [json] Run a single tool\n"); - printf(" codebase-memory-mcp install [-y|-n] [--force] [--dry-run]\n"); + printf(" codebase-memory-mcp install [-y|-n] [--force] [--dry-run] [--skill-mode=cli|mcp]\n"); printf(" codebase-memory-mcp uninstall [-y|-n] [--dry-run]\n"); printf(" codebase-memory-mcp update [-y|-n]\n"); printf(" codebase-memory-mcp config \n"); @@ -521,6 +523,7 @@ static void print_help(void) { printf(" --ui=false Disable HTTP graph visualization (persisted)\n"); printf(" --port=N Set UI port (default 9749, persisted)\n"); printf(" --tool-profile=analysis|scout Expose a restricted inspection surface\n"); + printf(" --idle-timeout=N Exit after N seconds without MCP requests\n"); printf("\nSupported automatic/conditional client surfaces (43):\n"); printf(" Claude Code, Codex CLI, Gemini CLI, Zed, OpenCode,\n"); printf(" Antigravity, Aider, KiloCode, VS Code, Cursor, Windsurf,\n"); @@ -608,6 +611,30 @@ static bool parse_ui_flags(int argc, char **argv, cbm_ui_config_t *cfg, bool *ex return changed; } +/* Optional whole-server idle timeout. Default 0 keeps permanent MCP sessions. + * CBM_IDLE_TIMEOUT_S supports wrappers that launch a stdio server from skills + * and want it to self-exit after intermittent requests stop. */ +static int parse_idle_timeout(int argc, char **argv) { + int timeout_s = 0; + char env_buf[128]; + const char *env = cbm_safe_getenv("CBM_IDLE_TIMEOUT_S", env_buf, sizeof(env_buf), NULL); + if (env && env[0]) { + long v = strtol(env, NULL, CBM_DECIMAL_BASE); + if (v > 0 && v < MAIN_MAX_PORT) { + timeout_s = (int)v; + } + } + for (int i = SKIP_ONE; i < argc; i++) { + if (strncmp(argv[i], "--idle-timeout=", SLEN("--idle-timeout=")) == 0) { + long v = strtol(argv[i] + MAIN_IDLE_TIMEOUT_OFF, NULL, CBM_DECIMAL_BASE); + if (v > 0 && v < MAIN_MAX_PORT) { + timeout_s = (int)v; + } + } + } + return timeout_s; +} + /* Install platform-specific signal handlers. */ static void setup_signal_handlers(void) { #ifdef _WIN32 @@ -827,7 +854,11 @@ int main(int argc, char **argv) { } /* Run MCP event loop (blocks until EOF or signal) */ - int rc = cbm_mcp_server_run(g_server, stdin, stdout); + int idle_timeout_s = parse_idle_timeout(argc, argv); + if (idle_timeout_s > 0) { + cbm_log_int(CBM_LOG_INFO, "server.idle_timeout.enabled", "seconds", idle_timeout_s); + } + int rc = cbm_mcp_server_run_with_idle_timeout(g_server, stdin, stdout, idle_timeout_s); atomic_store(&g_shutdown, 1); /* unblock the watchdog poll loop */ /* Shutdown */ diff --git a/src/mcp/mcp.c b/src/mcp/mcp.c index a04057419..9738d89db 100644 --- a/src/mcp/mcp.c +++ b/src/mcp/mcp.c @@ -9781,7 +9781,7 @@ static void handle_content_length_frame(cbm_mcp_server_t *srv, FILE *in, FILE *o #ifndef _WIN32 /* Unix 3-phase poll: non-blocking fd check, FILE* buffer peek, blocking poll. * Returns: 1 = data ready, 0 = timeout (evicted idle stores), -1 = error/EOF. */ -static int poll_for_input_unix(cbm_mcp_server_t *srv, int fd, FILE *in) { +static int poll_for_input_unix(cbm_mcp_server_t *srv, int fd, FILE *in, int timeout_s) { struct pollfd pfd = {.fd = fd, .events = POLLIN}; int pr = poll(&pfd, SKIP_ONE, 0); /* Phase 1: non-blocking */ @@ -9802,7 +9802,7 @@ static int poll_for_input_unix(cbm_mcp_server_t *srv, int fd, FILE *in) { return CBM_NOT_FOUND; } if (pr == 0) { - cbm_mcp_server_evict_idle(srv, STORE_IDLE_TIMEOUT_S); + cbm_mcp_server_evict_idle(srv, timeout_s); return 0; } return SKIP_ONE; @@ -9830,7 +9830,7 @@ static int poll_for_input_unix(cbm_mcp_server_t *srv, int fd, FILE *in) { return CBM_NOT_FOUND; } if (pr == 0) { - cbm_mcp_server_evict_idle(srv, STORE_IDLE_TIMEOUT_S); + cbm_mcp_server_evict_idle(srv, timeout_s); return 0; } return SKIP_ONE; @@ -9844,9 +9844,15 @@ static int poll_for_input_unix(cbm_mcp_server_t *srv, int fd, FILE *in) { /* ── Event loop ───────────────────────────────────────────────── */ int cbm_mcp_server_run(cbm_mcp_server_t *srv, FILE *in, FILE *out) { + return cbm_mcp_server_run_with_idle_timeout(srv, in, out, 0); +} + +int cbm_mcp_server_run_with_idle_timeout(cbm_mcp_server_t *srv, FILE *in, FILE *out, + int idle_timeout_s) { char *line = NULL; size_t cap = 0; int fd = cbm_fileno(in); + int poll_timeout_s = idle_timeout_s > 0 ? idle_timeout_s : STORE_IDLE_TIMEOUT_S; for (;;) { /* Poll with idle timeout so we can evict unused stores between requests. @@ -9870,20 +9876,28 @@ int cbm_mcp_server_run(cbm_mcp_server_t *srv, FILE *in, FILE *out) { #ifdef _WIN32 /* Windows: WaitForSingleObject on stdin handle */ HANDLE hStdin = (HANDLE)_get_osfhandle(fd); - DWORD wr = WaitForSingleObject(hStdin, STORE_IDLE_TIMEOUT_S * MCP_TIMEOUT_MS); + DWORD wr = WaitForSingleObject(hStdin, poll_timeout_s * MCP_TIMEOUT_MS); if (wr == WAIT_FAILED) { break; } if (wr == WAIT_TIMEOUT) { - cbm_mcp_server_evict_idle(srv, STORE_IDLE_TIMEOUT_S); + cbm_mcp_server_evict_idle(srv, poll_timeout_s); + if (idle_timeout_s > 0) { + cbm_log_int(CBM_LOG_INFO, "server.idle_timeout", "seconds", idle_timeout_s); + break; + } continue; } #else - int pr = poll_for_input_unix(srv, fd, in); + int pr = poll_for_input_unix(srv, fd, in, poll_timeout_s); if (pr < 0) { break; } if (pr == 0) { + if (idle_timeout_s > 0) { + cbm_log_int(CBM_LOG_INFO, "server.idle_timeout", "seconds", idle_timeout_s); + break; + } continue; /* timeout — idle stores evicted */ } #endif diff --git a/src/mcp/mcp.h b/src/mcp/mcp.h index 09a7ed1ec..7934d6f6d 100644 --- a/src/mcp/mcp.h +++ b/src/mcp/mcp.h @@ -127,6 +127,14 @@ void cbm_mcp_server_set_config(cbm_mcp_server_t *srv, struct cbm_config *cfg); * Blocks until EOF on input. Returns 0 on success, -1 on error. */ int cbm_mcp_server_run(cbm_mcp_server_t *srv, FILE *in, FILE *out); +/* Run the MCP stdio loop with an optional whole-server idle timeout. When + * idle_timeout_s > 0 and no request arrives for that many seconds, the loop + * exits cleanly. This is useful for skill/CLI launchers that do not keep a + * permanent MCP client connection open. idle_timeout_s <= 0 preserves the + * default long-running server behavior. */ +int cbm_mcp_server_run_with_idle_timeout(cbm_mcp_server_t *srv, FILE *in, FILE *out, + int idle_timeout_s); + /* Process a single JSON-RPC request line and return the response. * Returns heap-allocated JSON response string, or NULL for notifications. */ char *cbm_mcp_server_handle(cbm_mcp_server_t *srv, const char *line); diff --git a/tests/test_cli.c b/tests/test_cli.c index 8a5d39bfe..7ac2e4f23 100644 --- a/tests/test_cli.c +++ b/tests/test_cli.c @@ -834,6 +834,7 @@ TEST(cli_remove_old_monolithic_skill) { TEST(cli_skill_files_content) { /* Consolidated skill: all 4 former skills merged into one. */ + cbm_set_cli_skill_mode(false); const cbm_skill_t *sk = cbm_get_skills(); ASSERT_EQ(CBM_SKILL_COUNT, 1); ASSERT(strcmp(sk[0].name, "codebase-memory") == 0); @@ -862,6 +863,19 @@ TEST(cli_skill_files_content) { PASS(); } +TEST(cli_skill_mode_cli_content) { + cbm_set_cli_skill_mode(true); + const cbm_skill_t *sk = cbm_get_skills(); + ASSERT_EQ(CBM_SKILL_COUNT, 1); + ASSERT(strcmp(sk[0].name, "codebase-memory") == 0); + ASSERT(strstr(sk[0].content, "codebase-memory-mcp cli ") != NULL); + ASSERT(strstr(sk[0].content, "CLI first and only") != NULL); + ASSERT(strstr(sk[0].content, "14 MCP Tools") == NULL); + + cbm_set_cli_skill_mode(false); + PASS(); +} + TEST(cli_codex_instructions) { /* Port of TestCodexInstructionsCreation */ const char *instr = cbm_get_codex_instructions(); @@ -9728,7 +9742,7 @@ SUITE(cli) { /* Dry-run flag parsing (1 test — install_test.go) */ RUN_TEST(cli_dry_run_flags); - /* Skill management (7 tests — install_test.go) */ + /* Skill management (8 tests — install_test.go) */ RUN_TEST(cli_skill_creation); RUN_TEST(cli_skill_idempotent); RUN_TEST(cli_skill_force_overwrite); @@ -9739,6 +9753,7 @@ SUITE(cli) { RUN_TEST(cli_uninstall_removes_skills); RUN_TEST(cli_remove_old_monolithic_skill); RUN_TEST(cli_skill_files_content); + RUN_TEST(cli_skill_mode_cli_content); RUN_TEST(cli_codex_instructions); /* Editor MCP: Cursor/Windsurf/Gemini (5 tests — install_test.go) */ diff --git a/tests/test_mcp.c b/tests/test_mcp.c index 82b5469eb..4efa5a105 100644 --- a/tests/test_mcp.c +++ b/tests/test_mcp.c @@ -5036,6 +5036,33 @@ TEST(mcp_server_run_rapid_messages) { fclose(in_fp); PASS(); } + +TEST(mcp_server_run_idle_timeout_exits_without_eof) { + int fds[2]; + ASSERT_EQ(pipe(fds), 0); + + FILE *in_fp = fdopen(fds[0], "r"); + ASSERT_NOT_NULL(in_fp); + FILE *out_fp = tmpfile(); + ASSERT_NOT_NULL(out_fp); + + cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL); + ASSERT_NOT_NULL(srv); + + signal(SIGALRM, alarm_handler); + alarm(3); + int rc = cbm_mcp_server_run_with_idle_timeout(srv, in_fp, out_fp, 1); + alarm(0); + signal(SIGALRM, SIG_DFL); + + ASSERT_EQ(rc, 0); + + cbm_mcp_server_free(srv); + fclose(out_fp); + fclose(in_fp); + close(fds[1]); + PASS(); +} #endif /* !_WIN32 */ /* Issue #235: passing an unrecognised project name to a tool crashed the @@ -7109,6 +7136,7 @@ SUITE(mcp) { /* Poll/getline FILE* buffering fix */ #ifndef _WIN32 RUN_TEST(mcp_server_run_rapid_messages); + RUN_TEST(mcp_server_run_idle_timeout_exits_without_eof); #endif /* Snippet resolution (port of snippet_test.go) */ From 74f6ec0b8c20fe4f2a700850b3f671bdfad216df Mon Sep 17 00:00:00 2001 From: Alejandro Blanco-M Date: Sat, 4 Jul 2026 09:00:32 +0200 Subject: [PATCH 2/5] docs: explain MCP and skill install modes --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/README.md b/README.md index bb4919ed5..09b343d85 100644 --- a/README.md +++ b/README.md @@ -324,6 +324,44 @@ The `codebase-memory-mcp-bin` package is available at: https://aur.archlinux.org You: "Install this MCP server: https://github.com/DeusData/codebase-memory-mcp" ``` +### Install modes: MCP or Skill CLI + +`install` can configure agents in two modes: + +| Mode | Command | How it works | Use when | +|------|---------|--------------|----------| +| MCP mode | `codebase-memory-mcp install --skill-mode=mcp` | Installs MCP server config, Claude Code skill, and supported hooks. The agent starts `codebase-memory-mcp` as a stdio MCP server and calls tools over the MCP connection. This is the default. | Your agent supports MCP and you want direct tool calls. | +| Skill CLI mode | `codebase-memory-mcp install --skill-mode=cli` | Installs a Claude Code skill that instructs the agent to run `codebase-memory-mcp cli ''` through shell commands. It skips MCP config and hooks. | You want skill-only usage without maintaining an MCP connection or installing MCP config. | + +Default install is MCP mode: + +```bash +codebase-memory-mcp install +# same as: +codebase-memory-mcp install --skill-mode=mcp +``` + +Skill-only install: + +```bash +codebase-memory-mcp install --skill-mode=cli +``` + +Preview planned writes without changing files: + +```bash +codebase-memory-mcp install --plan --skill-mode=mcp +codebase-memory-mcp install --plan --skill-mode=cli +``` + +In MCP mode the process exits when stdin closes, on SIGTERM/SIGINT, or when its parent process exits. For wrappers that launch a stdio server intermittently, set an idle timeout so it self-exits after no requests: + +```bash +CBM_IDLE_TIMEOUT_S=60 codebase-memory-mcp +# or +codebase-memory-mcp --idle-timeout=60 +``` + ### Build from Source
@@ -534,6 +572,8 @@ codebase-memory-mcp cli query_graph '{"project": "my-project", "query": "MATCH ( codebase-memory-mcp cli --raw search_graph '{"project": "my-project", "label": "Function"}' | jq '.results[].name' ``` +This is also how Skill CLI mode works. The installed skill tells the LLM to create JSON arguments, run the `cli` subcommand through bash, and summarize the result. No MCP client connection is required for those requests. + ## MCP Tools ### Indexing From fd01670668d4442934d25244381d0a906d287311 Mon Sep 17 00:00:00 2001 From: Alejandro Blanco-M Date: Sat, 4 Jul 2026 09:21:42 +0200 Subject: [PATCH 3/5] fix: avoid deprecated list_projects skill command --- src/cli/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/cli.c b/src/cli/cli.c index 1e66888d2..732978420 100644 --- a/src/cli/cli.c +++ b/src/cli/cli.c @@ -619,7 +619,7 @@ static const char cli_skill_content[] = "```bash\n" "command -v codebase-memory-mcp\n" "codebase-memory-mcp --version\n" - "codebase-memory-mcp cli list_projects '{}'\n" + "printf '{}' | codebase-memory-mcp cli list_projects\n" "```\n" "\n" "If binary is missing, install binary-only, not MCP config:\n" From 7f9a2d9068c00cd1f2ea26d714a0765108675931 Mon Sep 17 00:00:00 2001 From: Alejandro Blanco-M Date: Sat, 4 Jul 2026 10:50:02 +0200 Subject: [PATCH 4/5] feat: strengthen CLI skill guidance --- src/cli/cli.c | 72 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 13 deletions(-) diff --git a/src/cli/cli.c b/src/cli/cli.c index 732978420..95afe6ee9 100644 --- a/src/cli/cli.c +++ b/src/cli/cli.c @@ -601,9 +601,11 @@ static const char mcp_skill_content[] = static const char cli_skill_content[] = "---\n" "name: codebase-memory\n" - "description: Use the codebase-memory-mcp binary through bash CLI commands for " - "codebase exploration, architecture, tracing, quality, and graph queries. CLI mode is " - "the only mechanism for this skill; do not use MCP tools unless the user explicitly asks.\n" + "description: Codebase knowledge graph expert. ALWAYS invoke this skill before code reviews, " + "before planning changes, when exploring unfamiliar code, when tracing impact, when checking " + "architecture, routes, dependencies, dead code, complexity, or refactor risk. Use this before " + "Grep, Glob, or file search for code discovery. CLI mode is the only mechanism for this skill; " + "do not use MCP tools unless the user explicitly asks.\n" "---\n" "\n" "# Codebase Memory — CLI Skill\n" @@ -615,6 +617,14 @@ static const char cli_skill_content[] = "codebase-memory-mcp cli ''\n" "```\n" "\n" + "## When to use this skill\n" + "Use this skill before grep/file search when the task is about code understanding. In particular:\n" + "- Before edits: map relevant functions, routes, files, dependencies, and likely blast radius.\n" + "- Before code review: inspect changed symbols, callers, callees, hotspots, and architectural impact.\n" + "- Before refactors: find usages, entry points, unreferenced code, and cross-service links.\n" + "- For architecture questions: summarize packages, dependencies, routes, clusters, and ADRs.\n" + "- For quality cleanup: find dead code candidates, high-degree functions, complex areas, and duplicates.\n" + "\n" "## First checks\n" "```bash\n" "command -v codebase-memory-mcp\n" @@ -622,16 +632,19 @@ static const char cli_skill_content[] = "printf '{}' | codebase-memory-mcp cli list_projects\n" "```\n" "\n" - "If binary is missing, install binary-only, not MCP config:\n" + "If binary is missing, ask the user before installing. Do not auto-install. If the user says yes, install binary-only, not MCP config:\n" "```bash\n" "curl -fsSL https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.sh | bash -s -- --skip-config\n" "```\n" "\n" - "## Project workflow\n" - "1. `list_projects` through CLI.\n" - "2. If current repo is absent, run `index_repository`.\n" - "3. Use `search_graph` before reading files.\n" - "4. Use `get_code_snippet` for exact source after finding `qualified_name`.\n" + "## Standard pre-analysis workflow\n" + "1. `list_projects` — see if current repo is already indexed and get exact project names.\n" + "2. `index_repository` — index or refresh the repo before analysis when missing or stale.\n" + "3. `get_graph_schema` — learn available labels, edge types, and properties before custom queries.\n" + "4. `search_graph` — find relevant functions, classes, routes, resources, or files before grep.\n" + "5. `trace_path` — inspect callers/callees and impact for the symbols you may edit.\n" + "6. `get_code_snippet` — read exact implementation after finding `qualified_name`.\n" + "7. `detect_changes` — for reviews, map git diff to changed symbols and risk labels.\n" "\n" "Safe JSON pattern:\n" "```bash\n" @@ -640,23 +653,56 @@ static const char cli_skill_content[] = "codebase-memory-mcp cli search_graph \"$json\"\n" "```\n" "\n" - "## Common commands\n" + "## Commands and what they are useful for\n" + "- `list_projects` — discover indexed repos and exact project names. Run first.\n" + "- `index_repository` — build or refresh graph data for a repo. Use before serious analysis.\n" + "- `index_status` — check whether indexing completed and how much graph data exists.\n" + "- `search_graph` — primary discovery command for symbols, routes, files, resources, and quality filters.\n" + "- `search_code` — indexed text search. Use for literals, error strings, config keys, or comments.\n" + "- `get_code_snippet` — read source for a qualified symbol without opening many files.\n" + "- `trace_path` — answer who calls this, what it calls, and what a change can affect.\n" + "- `detect_changes` — review helper for current git diff, changed symbols, blast radius, and risk.\n" + "- `get_architecture` — summarize packages, dependencies, routes, clusters, and hotspots.\n" + "- `get_graph_schema` — inspect labels, relationship types, and properties before Cypher.\n" + "- `query_graph` — custom Cypher-like read queries for advanced architecture/quality questions.\n" + "- `manage_adr` — read or update Architecture Decision Records. Ask before creating/updating ADRs.\n" + "- `ingest_traces` — import runtime traces to validate or enrich cross-service edges.\n" + "- `delete_project` — remove indexed graph data. Ask before running.\n" + "\n" + "## Common command templates\n" "```bash\n" + "# Index current repo when absent or stale\n" "codebase-memory-mcp cli index_repository '{\"repo_path\":\"/path/to/repo\",\"name\":\"my-project\",\"mode\":\"moderate\"}'\n" + "\n" + "# Find symbols or concepts before editing\n" "codebase-memory-mcp cli search_graph '{\"project\":\"PROJECT\",\"query\":\"update settings\",\"limit\":20}'\n" + "\n" + "# Find HTTP routes before changing API behavior\n" "codebase-memory-mcp cli search_graph '{\"project\":\"PROJECT\",\"label\":\"Route\",\"limit\":100}'\n" + "\n" + "# Read implementation after search_graph returns qualified_name\n" "codebase-memory-mcp cli get_code_snippet '{\"project\":\"PROJECT\",\"qualified_name\":\"pkg/orders.ProcessOrder\",\"include_neighbors\":true}'\n" + "\n" + "# Trace impact before edits or during review\n" "codebase-memory-mcp cli trace_path '{\"project\":\"PROJECT\",\"function_name\":\"ProcessOrder\",\"direction\":\"both\",\"depth\":3}'\n" - "codebase-memory-mcp cli get_architecture '{\"project\":\"PROJECT\",\"aspects\":[\"packages\",\"dependencies\",\"clusters\"]}'\n" + "\n" + "# Review current git diff with graph context\n" + "codebase-memory-mcp cli detect_changes '{\"project\":\"PROJECT\",\"repo_path\":\"/path/to/repo\"}'\n" + "\n" + "# Architecture overview\n" + "codebase-memory-mcp cli get_architecture '{\"project\":\"PROJECT\",\"aspects\":[\"packages\",\"dependencies\",\"clusters\",\"routes\"]}'\n" + "\n" + "# Advanced quality query\n" "codebase-memory-mcp cli query_graph '{\"project\":\"PROJECT\",\"query\":\"MATCH (f:Function) RETURN f.qualified_name LIMIT 20\",\"max_rows\":20}'\n" "```\n" "\n" "## Rules\n" "- CLI first and only for this skill.\n" + "- Use this before grep/file search for code discovery, review pre-analysis, and change planning.\n" "- Use `jq` for JSON creation and summarizing large outputs.\n" "- Save large raw outputs to `/tmp/cbm-*.json`.\n" - "- Fall back to file search only for non-indexed files or exact literals missing from graph.\n" - "- Ask before `delete_project`, `uninstall`, or global config mutation.\n"; + "- Fall back to file search only for non-indexed files, exact literals missing from graph, or generated/vendor files.\n" + "- Ask before `delete_project`, `uninstall`, global config mutation, or ADR writes.\n"; static const char codex_instructions_content[] = "# Codebase Knowledge Graph\n" From ac8cf2538cdccb8b427bd3039ad4693848438eb8 Mon Sep 17 00:00:00 2001 From: Alejandro Blanco-M Date: Wed, 22 Jul 2026 11:02:30 +0200 Subject: [PATCH 5/5] docs: sync CLI skill guidance with coverage checks --- src/cli/cli.c | 53 +++++++++++++++++++++++++++++++++++---------------- src/main.c | 7 ++++++- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/cli/cli.c b/src/cli/cli.c index 95afe6ee9..c6f023e34 100644 --- a/src/cli/cli.c +++ b/src/cli/cli.c @@ -601,17 +601,12 @@ static const char mcp_skill_content[] = static const char cli_skill_content[] = "---\n" "name: codebase-memory\n" - "description: Codebase knowledge graph expert. ALWAYS invoke this skill before code reviews, " - "before planning changes, when exploring unfamiliar code, when tracing impact, when checking " - "architecture, routes, dependencies, dead code, complexity, or refactor risk. Use this before " - "Grep, Glob, or file search for code discovery. CLI mode is the only mechanism for this skill; " - "do not use MCP tools unless the user explicitly asks.\n" + "description: Codebase knowledge graph expert. ALWAYS invoke this skill before code reviews, before planning changes, when exploring unfamiliar code, when tracing impact, when checking architecture, routes, dependencies, dead code, complexity, or refactor risk. Use this before Grep, Glob, or file search for code discovery. CLI mode is the only mechanism for this skill; do not use MCP tools unless the user explicitly asks.\n" "---\n" "\n" "# Codebase Memory — CLI Skill\n" "\n" - "Use `codebase-memory-mcp` as a normal executable. Do not rely on a permanent MCP " - "connection. Run each request through bash:\n" + "Use `codebase-memory-mcp` as a normal executable. Do not rely on a permanent MCP connection. Run each request through bash:\n" "\n" "```bash\n" "codebase-memory-mcp cli ''\n" @@ -620,7 +615,7 @@ static const char cli_skill_content[] = "## When to use this skill\n" "Use this skill before grep/file search when the task is about code understanding. In particular:\n" "- Before edits: map relevant functions, routes, files, dependencies, and likely blast radius.\n" - "- Before code review: inspect changed symbols, callers, callees, hotspots, and architectural impact.\n" + "- Before code review: inspect changed symbols, callers, callees, hotspots, coverage gaps, and architectural impact.\n" "- Before refactors: find usages, entry points, unreferenced code, and cross-service links.\n" "- For architecture questions: summarize packages, dependencies, routes, clusters, and ADRs.\n" "- For quality cleanup: find dead code candidates, high-degree functions, complex areas, and duplicates.\n" @@ -640,11 +635,13 @@ static const char cli_skill_content[] = "## Standard pre-analysis workflow\n" "1. `list_projects` — see if current repo is already indexed and get exact project names.\n" "2. `index_repository` — index or refresh the repo before analysis when missing or stale.\n" - "3. `get_graph_schema` — learn available labels, edge types, and properties before custom queries.\n" - "4. `search_graph` — find relevant functions, classes, routes, resources, or files before grep.\n" - "5. `trace_path` — inspect callers/callees and impact for the symbols you may edit.\n" - "6. `get_code_snippet` — read exact implementation after finding `qualified_name`.\n" - "7. `detect_changes` — for reviews, map git diff to changed symbols and risk labels.\n" + "3. `index_status` — confirm indexing completed and note current generation/freshness.\n" + "4. `get_graph_schema` — learn available labels, edge types, and properties before custom queries.\n" + "5. `search_graph` — find relevant functions, classes, routes, resources, or files before grep.\n" + "6. `trace_path` — inspect callers/callees and impact for the symbols you may edit.\n" + "7. `get_code_snippet` — read exact implementation after finding `qualified_name`.\n" + "8. `check_index_coverage` — check candidate source paths before making negative, exhaustive, or risky claims.\n" + "9. `detect_changes` — for reviews, map git diff to changed symbols and risk labels.\n" "\n" "Safe JSON pattern:\n" "```bash\n" @@ -653,6 +650,17 @@ static const char cli_skill_content[] = "codebase-memory-mcp cli search_graph \"$json\"\n" "```\n" "\n" + "## Evidence tiers\n" + "- **Scout (Tier 1):** fast positive lookup with few graph calls and targeted source checks. Treat results as provisional. Do not make absence, exhaustive, dead-code, or complete-impact claims.\n" + "- **Verify (Tier 2, default):** task-directed searches, relevant trace directions, exact snippets for material claims, all relevant result pages, and coverage checks for cited paths.\n" + "- **Auditor (Tier 3):** bounded-scope full verification with current graph generation, complete relevant pagination, both call directions and broader relationships when material, plus explicit unresolved limitations.\n" + "- **Every tier:** after candidate paths are known, call `check_index_coverage` once with every evidence path. For negative or exhaustive claims also include relevant scopes. A clean result means no recorded gap, not proof of completeness. For partial, skipped, excluded, stale, pending, or unknown coverage, read/grep the reported ranges or scope before relying on the graph.\n" + "\n" + "## Sessions and subagents\n" + "- At session start or after compaction, run `list_projects`/`index_status` before structural exploration.\n" + "- Before delegating, query the graph and coverage in the parent. Pass tier, exact project, generation/freshness, bounded scope, queries and pagination state, qualified symbols, paths, call-chain findings, coverage ranges/reasons, source fallback already performed, and unresolved questions to the child.\n" + "- A child without CLI access must not claim graph access. It should work from supplied evidence and use read/grep on exact source, especially every reported missed-coverage range.\n" + "\n" "## Commands and what they are useful for\n" "- `list_projects` — discover indexed repos and exact project names. Run first.\n" "- `index_repository` — build or refresh graph data for a repo. Use before serious analysis.\n" @@ -665,6 +673,7 @@ static const char cli_skill_content[] = "- `get_architecture` — summarize packages, dependencies, routes, clusters, and hotspots.\n" "- `get_graph_schema` — inspect labels, relationship types, and properties before Cypher.\n" "- `query_graph` — custom Cypher-like read queries for advanced architecture/quality questions.\n" + "- `check_index_coverage` — identify partial, skipped, excluded, stale, pending, or unknown indexed source ranges.\n" "- `manage_adr` — read or update Architecture Decision Records. Ask before creating/updating ADRs.\n" "- `ingest_traces` — import runtime traces to validate or enrich cross-service edges.\n" "- `delete_project` — remove indexed graph data. Ask before running.\n" @@ -686,6 +695,9 @@ static const char cli_skill_content[] = "# Trace impact before edits or during review\n" "codebase-memory-mcp cli trace_path '{\"project\":\"PROJECT\",\"function_name\":\"ProcessOrder\",\"direction\":\"both\",\"depth\":3}'\n" "\n" + "# Check coverage for cited source paths\n" + "codebase-memory-mcp cli check_index_coverage '{\"project\":\"PROJECT\",\"paths\":[\"src/orders.py\"]}'\n" + "\n" "# Review current git diff with graph context\n" "codebase-memory-mcp cli detect_changes '{\"project\":\"PROJECT\",\"repo_path\":\"/path/to/repo\"}'\n" "\n" @@ -696,14 +708,23 @@ static const char cli_skill_content[] = "codebase-memory-mcp cli query_graph '{\"project\":\"PROJECT\",\"query\":\"MATCH (f:Function) RETURN f.qualified_name LIMIT 20\",\"max_rows\":20}'\n" "```\n" "\n" + "## Edge types\n" + "CALLS, HTTP_CALLS, ASYNC_CALLS, DATA_FLOWS, IMPORTS, DEFINES, DEFINES_METHOD, HANDLES, IMPLEMENTS, OVERRIDE, USAGE, CONFIGURES, FILE_CHANGES_WITH, SIMILAR_TO, SEMANTICALLY_RELATED, CONTAINS_FILE, CONTAINS_FOLDER, CONTAINS_PACKAGE\n" + "\n" + "## Gotchas\n" + "1. `search_graph(relationship=\"HTTP_CALLS\")` filters nodes by degree. Use `query_graph` with Cypher to see actual edges.\n" + "2. `query_graph` has a 100k row ceiling. Add a Cypher `LIMIT` for broad queries or use `search_graph` pagination.\n" + "3. `trace_path` needs exact names. Use `search_graph(name_pattern=...)` first.\n" + "4. `direction=\"outbound\"` misses cross-service callers. Use `direction=\"both\"`.\n" + "5. `search_graph` results default to 50 per page. Check `has_more` and use `offset`.\n" + "\n" "## Rules\n" "- CLI first and only for this skill.\n" "- Use this before grep/file search for code discovery, review pre-analysis, and change planning.\n" "- Use `jq` for JSON creation and summarizing large outputs.\n" "- Save large raw outputs to `/tmp/cbm-*.json`.\n" - "- Fall back to file search only for non-indexed files, exact literals missing from graph, or generated/vendor files.\n" + "- Fall back to file search only for non-indexed files, exact literals missing from graph, coverage gaps, or generated/vendor files.\n" "- Ask before `delete_project`, `uninstall`, global config mutation, or ADR writes.\n"; - static const char codex_instructions_content[] = "# Codebase Knowledge Graph\n" "\n" @@ -7442,7 +7463,7 @@ int cbm_install_agent_configs(const char *home, const char *binary_path, bool fo if (!agents.claude_code && !g_install_plan) { printf("skill-mode: cli requested, but no Claude Code skills directory was detected.\n"); } - return; + return CLI_OK; } install_cli_agent_configs(&agents, home, binary_path, force, dry_run); install_editor_agent_configs(&agents, home, binary_path, force, dry_run); diff --git a/src/main.c b/src/main.c index ecf381afa..d7ce3dcc2 100644 --- a/src/main.c +++ b/src/main.c @@ -524,6 +524,11 @@ static void print_help(void) { printf(" --port=N Set UI port (default 9749, persisted)\n"); printf(" --tool-profile=analysis|scout Expose a restricted inspection surface\n"); printf(" --idle-timeout=N Exit after N seconds without MCP requests\n"); + printf("\nInstall modes:\n"); + printf(" --skill-mode=mcp Install MCP config, Claude Code skill, and supported hooks (default)\n"); + printf(" --skill-mode=cli Install only the Claude Code CLI skill and skip MCP configs/hooks\n"); + printf(" Claude skill path: ~/.claude/skills/codebase-memory/SKILL.md\n"); + printf(" Shared skill path for supported clients: ~/.agents/skills/codebase-memory/SKILL.md\n"); printf("\nSupported automatic/conditional client surfaces (43):\n"); printf(" Claude Code, Codex CLI, Gemini CLI, Zed, OpenCode,\n"); printf(" Antigravity, Aider, KiloCode, VS Code, Cursor, Windsurf,\n"); @@ -542,7 +547,7 @@ static void print_help(void) { printf("\nTools: index_repository, search_graph, query_graph, trace_path,\n"); printf(" get_code_snippet, get_graph_schema, get_architecture, search_code,\n"); printf(" list_projects, delete_project, index_status, detect_changes,\n"); - printf(" manage_adr, ingest_traces\n"); + printf(" check_index_coverage, manage_adr, ingest_traces\n"); } /* ── Main ───────────────────────────────────────────────────────── */