diff --git a/README.md b/README.md index 7cdac83bf1..372ca95aae 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [](#mcp-tools) [](https://github.com/DeusData/codebase-memory-mcp) [](#hybrid-lsp) -[](https://github.com/DeusData/codebase-memory-mcp) +[](https://github.com/DeusData/codebase-memory-mcp) [](https://github.com/DeusData/codebase-memory-mcp) [](https://github.com/DeusData/codebase-memory-mcp/releases/latest) [](https://scorecard.dev/viewer/?uri=github.com/DeusData/codebase-memory-mcp) @@ -20,7 +20,7 @@ Give your coding agent structural context through [Model Context Protocol](https://modelcontextprotocol.io/) (MCP), or query the graph directly from the CLI. **The MCP server is the core of the project.** Its optional visual companion, **CBM Lens, is in open beta (v0.0.1)**, with a source reader, architecture maps, the Galaxy graph, coverage diagnostics, architecture decisions, and local code chat. -The native C backend exposes **17 MCP tools**, parses [languages and file formats](#language-support) with vendored grammars, and adds [Hybrid LSP](#hybrid-lsp) type resolution for supported languages. The installer supports **45 automatic or conditional client integrations**, including client configuration, skills, agent profiles, and applicable hooks. Indexing and graph queries need no hosted service or API key. Focused graph queries can reduce repeated file reads and token use; see the [measured results](#performance). +The native C backend exposes **17 MCP tools**, parses [languages and file formats](#language-support) with vendored grammars, and adds [Hybrid LSP](#hybrid-lsp) type resolution for supported languages. The installer supports **46 automatic or conditional client integrations**, including client configuration, skills, agent profiles, and applicable hooks. Indexing and graph queries need no hosted service or API key. Focused graph queries can reduce repeated file reads and token use; see the [measured results](#performance). [Quick start](#quick-start) · [Capabilities](#features) · [Documentation](#documentation-connected-to-code) · [Infrastructure](#infrastructure-and-deployment) · [Smart CI](#smart-ci-and-test-impact) · [Explore CBM Lens](#cbm-lens-beta-code-visualization-and-source-navigation) · [Agent setup](#agent-setup-skills-profiles-and-hooks) · [MCP tools](#mcp-tools) @@ -671,7 +671,7 @@ Restart your agent. Verify with `/mcp`; you should see `codebase-memory-mcp` wit ## Multi-Agent Support -`install` configures **45 supported automatic/conditional client surfaces**: 39 detected automatically and 6 +`install` configures **46 supported automatic/conditional client surfaces**: 40 detected automatically and 6 conditional or explicit. “Conditional” means the installer writes only when the documented platform or an explicit, already-existing config path proves the target is active. It never flips experimental feature flags, enables plugins, @@ -739,6 +739,7 @@ overwrite user-modified agents. | IBM Bob IDE | Conditional | Existing `~/.bob/mcp.json` | Shared rule + IDE skill; no invented hook or agent | | Oh My Pi (omp) | Detected | Effective agent directory (`OMP_PROFILE` / `PI_CODING_AGENT_DIR`; default `~/.omp/agent/mcp.json`) | Skill and three direct-MCP graph-tool subagents (Scout/Verify/Auditor); preserves user `AGENTS.md` | | Sourcegraph Cody | Explicit opt-in | Existing `$CBM_CODY_CONFIG_PATH` | MCP only | +| ZCode | Detected | `~/.zcode/cli/config.json` (`mcp.servers`) | `~/.zcode/AGENTS.md` + skill; hooks withheld because config-file hooks require a global `hooks.enabled` opt-in the installer must not flip, and subagents have no documented config-file surface | For Codex, install keeps only a tiny managed activation pointer in global `$CODEX_HOME/AGENTS.md`; all detailed behavior lives in the installed `codebase-memory` skill. @@ -1090,7 +1091,7 @@ src/ main.c Entry point (MCP stdio server + CLI + install/update/config) daemon/ Per-account session coordination, IPC, lifecycle, shared jobs/watchers mcp/ MCP server (17 tools, JSON-RPC 2.0, session detection, auto-index) - cli/ Install/uninstall/update/config (45 client surfaces, hooks, instructions) + cli/ Install/uninstall/update/config (46 client surfaces, hooks, instructions) store/ SQLite graph storage (nodes, edges, traversal, search, Louvain) pipeline/ Multi-pass indexing (structure → definitions → calls → HTTP links → config → tests) cypher/ Cypher query lexer, parser, planner, executor diff --git a/docs/index.html b/docs/index.html index fac7e3b5b1..dfb0718561 100644 --- a/docs/index.html +++ b/docs/index.html @@ -71,7 +71,7 @@ "Cross-service and cross-repository linking", "Change impact, coverage diagnostics and optional runtime trace ingestion", "Experimental C/C++ test selection using static relationships and admitted per-test coverage", - "45 automatic/conditional client surfaces with client-specific setup", + "46 automatic/conditional client surfaces with client-specific setup", "Optional browser AI with on-device inference" ], "author": { @@ -363,7 +363,7 @@
Agent setup
The installer configures 45 automatic/conditional client surfaces. MCP connections, instructions, skills, profiles and hooks are installed according to each client's supported capabilities.
Agent setup
The installer configures 46 automatic/conditional client surfaces. MCP connections, instructions, skills, profiles and hooks are installed according to each client's supported capabilities.
Configure supported integrations for clients such as Claude Code, Codex CLI, Gemini CLI, Cursor, Zed and OpenCode.
Skills and instructions guide graph queries, coverage checks and source verification. Supported custom-agent formats get Scout, Verify and Auditor profiles.
Client-specific lifecycle and read/search hooks help surface relevant graph context. Hook and subagent capabilities differ by client and platform.
Detected automatically (39): Claude Code, Codex CLI, Gemini CLI, Zed, OpenCode, Antigravity, Aider, KiloCode, VS Code, Cursor, Windsurf, Augment / Auggie, OpenClaw, Kiro, Junie, Hermes, OpenHands, Cline, Warp, Qwen Code, GitHub Copilot CLI, Factory Droid, Crush, Goose, Mistral Vibe, Grok Build, Qoder CLI, Kimi Code CLI, GitLab Duo CLI, Rovo Dev CLI, Amp, Devin CLI / Local, Tabnine, Amazon Q Developer IDE, CodeBuddy Code CLI, IBM Bob Shell, Pochi, Pi, and Oh My Pi (omp).
+Detected automatically (40): Claude Code, Codex CLI, Gemini CLI, Zed, OpenCode, Antigravity, Aider, KiloCode, VS Code, Cursor, Windsurf, Augment / Auggie, OpenClaw, Kiro, Junie, Hermes, OpenHands, Cline, Warp, Qwen Code, GitHub Copilot CLI, Factory Droid, Crush, Goose, Mistral Vibe, Grok Build, Qoder CLI, Kimi Code CLI, GitLab Duo CLI, Rovo Dev CLI, Amp, Devin CLI / Local, Tabnine, Amazon Q Developer IDE, CodeBuddy Code CLI, IBM Bob Shell, Pochi, Pi, Oh My Pi (omp), and ZCode.
Conditional or explicit (6): Continue / cn, Visual Studio, TRAE, Roo Code, IBM Bob IDE, and Sourcegraph Cody.
These are client surfaces, not a promise of identical MCP, hook or subagent support. For example, Pi receives instructions and a skill; Warp's detected skill installation is counted while its MCP connection remains manual. Other manual, UI, cloud and repository-managed integrations are documented separately.
diff --git a/docs/llms.txt b/docs/llms.txt index 83829939fc..d0f8a1c1c6 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -14,14 +14,14 @@ - Smart CI (experimental): repository-specific changed-file rules select CI jobs; test-impact select combines static reachability, changed tests, and admitted per-test coverage to select tests. Narrowing supports C/C++ family code with supported test-harness conventions and an explicitly configured runner. Missing or doubtful evidence broadens the selection or runs all tests. Other indexed languages are not automatically supported for test narrowing. test-impact publish creates the baseline graph-and-coverage bundle. Start in shadow mode; consult the guide for version requirements. - Architecture decisions and runtime evidence: manage_adr stores the project's decision record; ingest_traces adds separately collected observed call relationships alongside static analysis. - CBM Lens open beta (v0.0.1): optional visual companion to the MCP server, with a local browser interface with source exploration, Galaxy graph navigation, architecture and behavior views, ADR editing, index-coverage diagnostics, daemon/system information, and optional local code chat. Static graph animations are not live execution traces. Activity overlays are experimental source-build features, not a standard-release promise. -- Agent integrations: 45 automatic or conditional client surfaces. Installation configures supported MCP entries, durable instructions, skills, Scout/Verify/Auditor profiles, and applicable session, compaction, search, and read hooks. Coverage and activation vary by client and platform. Hooks provide context without blocking or replacing the user's tool call; some clients require trust or use parent-agent handoff instead. +- Agent integrations: 46 automatic or conditional client surfaces. Installation configures supported MCP entries, durable instructions, skills, Scout/Verify/Auditor profiles, and applicable session, compaction, search, and read hooks. Coverage and activation vary by client and platform. Hooks provide context without blocking or replacing the user's tool call; some clients require trust or use parent-agent handoff instead. - Persistent operation: a per-account daemon coordinates sessions and indexing. Existing indexes can stay synchronized during eligible active MCP sessions; initial auto-indexing is separately opt-in. Optional team graph artifacts share a baseline while each user keeps a local query database. - Token-efficient retrieval: targeted graph queries, focused source snippets, and compact, paged results can reduce token use and repeated file reads during code exploration. - Distribution: native C backend, MIT license, standard native releases for macOS, Linux, and Windows include CBM Lens; Linux portable archives provide static builds. Nix and development source builds can omit the UI. Package-manager options and release verification are documented in the README. ## Client coverage -- Supported agents: 45 automatic/conditional client surfaces (39 automatically detected + 6 conditional/explicit): Claude Code, Codex CLI, Gemini CLI, Zed, OpenCode, Antigravity, Aider, KiloCode, VS Code, Cursor, Windsurf, Augment / Auggie, OpenClaw, Kiro, Junie, Hermes, OpenHands, Cline, Warp, Qwen Code, GitHub Copilot CLI, Factory Droid, Crush, Goose, Mistral Vibe, Grok Build, Qoder CLI, Kimi Code CLI, GitLab Duo CLI, Rovo Dev CLI, Amp, Devin CLI / Local, Tabnine, Continue / cn, Visual Studio, TRAE, Roo Code, Amazon Q Developer IDE, CodeBuddy Code CLI, IBM Bob IDE, IBM Bob Shell, Pochi, Pi, Oh My Pi (omp), and Sourcegraph Cody. +- Supported agents: 46 automatic/conditional client surfaces (40 automatically detected + 6 conditional/explicit): Claude Code, Codex CLI, Gemini CLI, Zed, OpenCode, Antigravity, Aider, KiloCode, VS Code, Cursor, Windsurf, Augment / Auggie, OpenClaw, Kiro, Junie, Hermes, OpenHands, Cline, Warp, Qwen Code, GitHub Copilot CLI, Factory Droid, Crush, Goose, Mistral Vibe, Grok Build, Qoder CLI, Kimi Code CLI, GitLab Duo CLI, Rovo Dev CLI, Amp, Devin CLI / Local, Tabnine, Continue / cn, Visual Studio, TRAE, Roo Code, Amazon Q Developer IDE, CodeBuddy Code CLI, IBM Bob IDE, IBM Bob Shell, Pochi, Pi, Oh My Pi (omp), Sourcegraph Cody, and ZCode. ## Measurement and limits diff --git a/pkg/npm/README.md b/pkg/npm/README.md index bce4d52177..f9110bc33c 100644 --- a/pkg/npm/README.md +++ b/pkg/npm/README.md @@ -7,7 +7,7 @@ **Local code intelligence for developers and AI agents.** CBM connects source code, documentation and infrastructure in a persistent knowledge graph. Explore through MCP, the CLI or CBM Lens to find code, trace dependencies and deployments, and understand what a change affects. -This npm wrapper downloads, verifies and caches the native executable, its authenticated integration asset and the content-addressed Lens frontend pack. Node.js manages installation and launch; the graph is processed locally without a hosted service or API key. The installer offers 45 client integration targets, with capabilities that vary by client. +This npm wrapper downloads, verifies and caches the native executable, its authenticated integration asset and the content-addressed Lens frontend pack. Node.js manages installation and launch; the graph is processed locally without a hosted service or API key. The installer offers 46 client integration targets, with capabilities that vary by client. ## Installation @@ -35,8 +35,8 @@ Restart your agent and ask it to **"Index this project"**. Use `codebase-memory- - **Focused context** — retrieve symbols, relationships and bounded source excerpts instead of repeatedly reading whole files. Savings depend on the task and workflow. - **CBM Lens** — explore source, architecture, call paths, decisions and index coverage in a local browser workspace. - **Smart CI** — experimental change-aware test selection for supported C/C++ test runners, using graph relationships and admitted per-test coverage. Missing evidence broadens execution. See the [Smart CI guide](https://github.com/DeusData/codebase-memory-mcp/blob/main/docs/SMART_CI.md). -- **45 supported automatic/conditional client surfaces** — `install` configures the appropriate MCP, durable-context, and documented hook surfaces without widening client permissions. -- **Detected automatically (39)** — Claude Code, Codex CLI, Gemini CLI, Zed, OpenCode, Antigravity, Aider, KiloCode, VS Code, Cursor, Windsurf, Augment / Auggie, OpenClaw, Kiro, Junie, Hermes, OpenHands, Cline, Warp, Qwen Code, GitHub Copilot CLI, Factory Droid, Crush, Goose, Mistral Vibe, Grok Build, Qoder CLI, Kimi Code CLI, GitLab Duo CLI, Rovo Dev CLI, Amp, Devin CLI / Local, Tabnine, Amazon Q Developer IDE, CodeBuddy Code CLI, IBM Bob Shell, Pochi, Pi, and Oh My Pi (omp). +- **46 supported automatic/conditional client surfaces** — `install` configures the appropriate MCP, durable-context, and documented hook surfaces without widening client permissions. +- **Detected automatically (40)** — Claude Code, Codex CLI, Gemini CLI, Zed, OpenCode, Antigravity, Aider, KiloCode, VS Code, Cursor, Windsurf, Augment / Auggie, OpenClaw, Kiro, Junie, Hermes, OpenHands, Cline, Warp, Qwen Code, GitHub Copilot CLI, Factory Droid, Crush, Goose, Mistral Vibe, Grok Build, Qoder CLI, Kimi Code CLI, GitLab Duo CLI, Rovo Dev CLI, Amp, Devin CLI / Local, Tabnine, Amazon Q Developer IDE, CodeBuddy Code CLI, IBM Bob Shell, Pochi, Pi, Oh My Pi (omp), and ZCode. - **Conditional or explicit (6)** — Continue / cn, Visual Studio, TRAE, Roo Code, IBM Bob IDE, and Sourcegraph Cody. Bob IDE is touched only when `~/.bob/mcp.json` already exists. - **New documented adapters** — CodeBuddy uses `~/.codebuddy/.mcp.json` while preserving active older files; Bob Shell uses `~/.bob/mcp_settings.json`; Pochi uses the `mcp` section in `~/.pochi/config.jsonc`; Amazon Q Developer IDE defaults to `~/.aws/amazonq/default.json` while preserving either documented alternative. - **Lifecycle hooks stay conservative** — Kimi uses `UserPromptSubmit`; on macOS/Linux, GitLab Duo gets a fail-open user `SessionStart`, while Devin gets `UserPromptSubmit`, `PostCompaction`, and a deduplicated `SessionStart` when Claude does not already provide it. Qoder, GitLab Duo, Devin, and Factory hooks are withheld on Windows without a documented shell/executor contract. Cline's auto-activating file hooks are withheld because their context output is not reliably consumed, CodeBuddy beta hooks are not auto-installed, and Cursor context hooks remain withheld. diff --git a/src/cli/agent_clients.c b/src/cli/agent_clients.c index 4c07a583d1..79d7b24b07 100644 --- a/src/cli/agent_clients.c +++ b/src/cli/agent_clients.c @@ -76,6 +76,9 @@ static const cbm_agent_client_profile_t agent_profiles[CBM_AGENT_CLIENT_COUNT] = {CBM_AGENT_CLIENT_OMP, "omp", "Oh My Pi (omp)", CBM_AGENT_STABLE, CBM_AGENT_CAP_MCP | CBM_AGENT_CAP_SKILL | CBM_AGENT_CAP_AGENT, "omp", agent_install_callback, agent_remove_callback}, + {CBM_AGENT_CLIENT_ZCODE, "zcode", "ZCode", CBM_AGENT_STABLE, + CBM_AGENT_CAP_MCP | CBM_AGENT_CAP_INSTRUCTIONS | CBM_AGENT_CAP_SKILL, "zcode", + agent_install_callback, agent_remove_callback}, }; size_t cbm_agent_client_count(void) { @@ -590,6 +593,12 @@ int cbm_agent_client_resolve_path(cbm_agent_client_id_t id, return written >= 0 && (size_t)written < path_out_size ? 0 : -1; } return agent_join_path(path_out, path_out_size, options->home_dir, ".omp/agent/mcp.json"); + case CBM_AGENT_CLIENT_ZCODE: + /* ZCode keeps user-scope MCP servers in the nested mcp.servers object + * of its CLI config file; the same-scope .agents/mcp.json fallback is + * read only when this file has no servers and is never written. */ + return agent_join_path(path_out, path_out_size, options->home_dir, + ".zcode/cli/config.json"); case CBM_AGENT_CLIENT_PI: return 1; case CBM_AGENT_CLIENT_SOURCEGRAPH_CODY: @@ -661,6 +670,8 @@ static int agent_client_marker_path(cbm_agent_client_id_t id, return written >= 0 && (size_t)written < path_out_size ? 0 : 1; } return agent_join_path(path_out, path_out_size, options->home_dir, ".omp/agent"); + case CBM_AGENT_CLIENT_ZCODE: + return agent_join_path(path_out, path_out_size, options->home_dir, ".zcode"); case CBM_AGENT_CLIENT_PI: return agent_join_path(path_out, path_out_size, options->home_dir, ".pi/agent"); default: @@ -993,41 +1004,6 @@ static int agent_json_find_member(const char *data, size_t object_start, size_t } } -static int agent_json_find_entry(const char *document, size_t length, const char *section, - size_t *entry_start, size_t *entry_end) { - size_t bom = length >= 3U && (unsigned char)document[0] == 0xefU && - (unsigned char)document[1] == 0xbbU && (unsigned char)document[2] == 0xbfU - ? 3U - : 0U; - agent_json_scanner_t scanner = {.data = document, .length = length, .position = bom}; - if (agent_json_skip_space(&scanner) != 0) { - return -1; - } - size_t root_start = scanner.position; - if (agent_json_scan_value(&scanner, 0U) != 0 || agent_json_skip_space(&scanner) != 0 || - scanner.position != length || document[root_start] != '{') { - return -1; - } - size_t object_start = root_start; - size_t object_end = scanner.position; - if (section) { - size_t section_start = 0U; - size_t section_end = 0U; - int section_result = agent_json_find_member(document, root_start, object_end, section, - §ion_start, §ion_end); - if (section_result != 0) { - return section_result; - } - if (document[section_start] != '{') { - return -1; - } - object_start = section_start; - object_end = section_end; - } - return agent_json_find_member(document, object_start, object_end, AGENT_ENTRY_KEY, entry_start, - entry_end); -} - static char *agent_json_escape(const char *value) { size_t length = strlen(value); if (length > (SIZE_MAX - 1U) / 6U) { @@ -1080,7 +1056,7 @@ static char *agent_json_canonical(cbm_agent_client_id_t id, const char *binary_p } const char *extra = ""; if (id == CBM_AGENT_CLIENT_GITLAB_DUO || id == CBM_AGENT_CLIENT_VISUAL_STUDIO || - id == CBM_AGENT_CLIENT_OMP) { + id == CBM_AGENT_CLIENT_OMP || id == CBM_AGENT_CLIENT_ZCODE) { extra = ", \"type\": \"stdio\""; } else if (id == CBM_AGENT_CLIENT_ROVO_DEV) { extra = ", \"transport\": \"stdio\""; @@ -1153,20 +1129,67 @@ static bool agent_json_owned(const char *document, size_t start, size_t end, return equal; } -static const char *agent_json_section(cbm_agent_client_id_t id) { +enum { AGENT_JSON_SECTION_MAX = 2U }; + +/* Resolves the object path holding the server map and returns its depth; 0 + * means the document root. ZCode nests its server map two levels deep under + * mcp.servers while every other JSON client uses one top-level section key. */ +static size_t agent_json_section_path(cbm_agent_client_id_t id, + const char *path[AGENT_JSON_SECTION_MAX]) { if (id == CBM_AGENT_CLIENT_AMP) { - return NULL; + return 0U; + } + if (id == CBM_AGENT_CLIENT_ZCODE) { + path[0] = "mcp"; + path[1] = "servers"; + return 2U; } if (id == CBM_AGENT_CLIENT_VISUAL_STUDIO) { - return "servers"; + path[0] = "servers"; + } else if (id == CBM_AGENT_CLIENT_SOURCEGRAPH_CODY) { + path[0] = "cody.mcpServers"; + } else if (id == CBM_AGENT_CLIENT_POCHI) { + path[0] = "mcp"; + } else { + path[0] = "mcpServers"; } - if (id == CBM_AGENT_CLIENT_SOURCEGRAPH_CODY) { - return "cody.mcpServers"; + return 1U; +} + +static int agent_json_find_entry(const char *document, size_t length, + const char *const *section_path, size_t section_depth, + size_t *entry_start, size_t *entry_end) { + size_t bom = length >= 3U && (unsigned char)document[0] == 0xefU && + (unsigned char)document[1] == 0xbbU && (unsigned char)document[2] == 0xbfU + ? 3U + : 0U; + agent_json_scanner_t scanner = {.data = document, .length = length, .position = bom}; + if (agent_json_skip_space(&scanner) != 0) { + return -1; } - if (id == CBM_AGENT_CLIENT_POCHI) { - return "mcp"; + size_t root_start = scanner.position; + if (agent_json_scan_value(&scanner, 0U) != 0 || agent_json_skip_space(&scanner) != 0 || + scanner.position != length || document[root_start] != '{') { + return -1; + } + size_t object_start = root_start; + size_t object_end = scanner.position; + for (size_t depth = 0U; depth < section_depth; depth++) { + size_t section_start = 0U; + size_t section_end = 0U; + int section_result = agent_json_find_member( + document, object_start, object_end, section_path[depth], §ion_start, §ion_end); + if (section_result != 0) { + return section_result; + } + if (document[section_start] != '{') { + return -1; + } + object_start = section_start; + object_end = section_end; } - return "mcpServers"; + return agent_json_find_member(document, object_start, object_end, AGENT_ENTRY_KEY, entry_start, + entry_end); } static int agent_json_edit(cbm_agent_client_id_t id, const char *config_path, @@ -1175,6 +1198,8 @@ static int agent_json_edit(cbm_agent_client_id_t id, const char *config_path, if (!canonical) { return CBM_AGENT_EDIT_ERROR; } + const char *section_path[AGENT_JSON_SECTION_MAX]; + size_t section_depth = agent_json_section_path(id, section_path); char *document = NULL; size_t length = 0U; int read_result = cbm_json_like_read_document(config_path, &document, &length); @@ -1186,7 +1211,7 @@ static int agent_json_edit(cbm_agent_client_id_t id, const char *config_path, size_t entry_end = 0U; int find_result = read_result == 1 ? 1 - : agent_json_find_entry(document, length, agent_json_section(id), + : agent_json_find_entry(document, length, section_path, section_depth, &entry_start, &entry_end); if (find_result < 0) { free(document); @@ -1203,13 +1228,11 @@ static int agent_json_edit(cbm_agent_client_id_t id, const char *config_path, free(canonical); return CBM_AGENT_EDIT_OK; } - const char *section = agent_json_section(id); - const char *path[1] = {section}; int edit_result = remove - ? cbm_json_like_remove_entry_if_unchanged(config_path, path, section ? 1U : 0U, + ? cbm_json_like_remove_entry_if_unchanged(config_path, section_path, section_depth, AGENT_ENTRY_KEY, document, length) - : cbm_json_like_upsert_entry_if_unchanged(config_path, path, section ? 1U : 0U, + : cbm_json_like_upsert_entry_if_unchanged(config_path, section_path, section_depth, AGENT_ENTRY_KEY, canonical, read_result == 1 ? NULL : document, length); free(document); @@ -1462,6 +1485,7 @@ static bool agent_json_client(cbm_agent_client_id_t id) { case CBM_AGENT_CLIENT_AMAZON_Q: case CBM_AGENT_CLIENT_CODEBUDDY: case CBM_AGENT_CLIENT_OMP: + case CBM_AGENT_CLIENT_ZCODE: case CBM_AGENT_CLIENT_IBM_BOB_IDE: case CBM_AGENT_CLIENT_IBM_BOB_SHELL: case CBM_AGENT_CLIENT_POCHI: diff --git a/src/cli/agent_clients.h b/src/cli/agent_clients.h index ccf9f81a16..695e0a7163 100644 --- a/src/cli/agent_clients.h +++ b/src/cli/agent_clients.h @@ -32,6 +32,7 @@ typedef enum { CBM_AGENT_CLIENT_PI, CBM_AGENT_CLIENT_SOURCEGRAPH_CODY, CBM_AGENT_CLIENT_OMP, + CBM_AGENT_CLIENT_ZCODE, CBM_AGENT_CLIENT_COUNT } cbm_agent_client_id_t; diff --git a/src/cli/cli.c b/src/cli/cli.c index 1ebe1687e8..897ceae2d7 100644 --- a/src/cli/cli.c +++ b/src/cli/cli.c @@ -9594,6 +9594,15 @@ static void install_omp_durable_context(const cbm_agent_registry_context_t *regi dry_run); } +static void install_zcode_durable_context(const char *home, bool force, bool dry_run) { + char instructions_path[CLI_BUF_1K]; + char skills_dir[CLI_BUF_1K]; + snprintf(instructions_path, sizeof(instructions_path), "%s/.zcode/AGENTS.md", home); + snprintf(skills_dir, sizeof(skills_dir), "%s/.zcode/skills", home); + install_managed_agent_instructions("ZCode", instructions_path, dry_run); + install_agent_skill("ZCode", skills_dir, force, dry_run); +} + static void install_agent_client_registry(const char *home, const char *binary_path, bool inherit_claude_session, bool force, bool dry_run) { cbm_agent_registry_context_t registry; @@ -9667,6 +9676,8 @@ static void install_agent_client_registry(const char *home, const char *binary_p install_pi_durable_context(home, binary_path, force, dry_run); } else if (profile->id == CBM_AGENT_CLIENT_OMP) { install_omp_durable_context(®istry, force, dry_run); + } else if (profile->id == CBM_AGENT_CLIENT_ZCODE) { + install_zcode_durable_context(home, force, dry_run); } } } @@ -12095,6 +12106,15 @@ static void uninstall_omp_durable_context(const cbm_agent_registry_context_t *re dry_run); } +static void uninstall_zcode_durable_context(const char *home, bool dry_run) { + char instructions_path[CLI_BUF_1K]; + char skills_dir[CLI_BUF_1K]; + snprintf(instructions_path, sizeof(instructions_path), "%s/.zcode/AGENTS.md", home); + snprintf(skills_dir, sizeof(skills_dir), "%s/.zcode/skills", home); + uninstall_managed_agent_instructions("ZCode", instructions_path, dry_run); + uninstall_agent_skill("ZCode", skills_dir, dry_run); +} + static void uninstall_agent_client_registry(const char *home, bool dry_run) { cbm_agent_registry_context_t registry; cbm_init_agent_registry_context(home, ®istry); @@ -12156,6 +12176,8 @@ static void uninstall_agent_client_registry(const char *home, bool dry_run) { uninstall_pi_durable_context(home, dry_run); } else if (profile->id == CBM_AGENT_CLIENT_OMP) { uninstall_omp_durable_context(®istry, dry_run); + } else if (profile->id == CBM_AGENT_CLIENT_ZCODE) { + uninstall_zcode_durable_context(home, dry_run); } } } diff --git a/src/main.c b/src/main.c index 3e658c8016..58213286c8 100644 --- a/src/main.c +++ b/src/main.c @@ -1147,7 +1147,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("\nSupported automatic/conditional client surfaces (45):\n"); + printf("\nSupported automatic/conditional client surfaces (46):\n"); printf(" Claude Code, Codex CLI, Gemini CLI, Zed, OpenCode,\n"); printf(" Antigravity, Aider, KiloCode, VS Code, Cursor, Windsurf,\n"); printf(" Augment / Auggie, OpenClaw, Kiro, Junie, Hermes, OpenHands,\n"); @@ -1156,7 +1156,7 @@ static void print_help(void) { printf(" Rovo Dev CLI, Amp, Devin CLI / Local, Tabnine, Continue / cn,\n"); printf(" Visual Studio, TRAE, Roo Code, Amazon Q Developer IDE,\n"); printf(" CodeBuddy Code CLI, IBM Bob IDE, IBM Bob Shell, Pochi, Pi,\n"); - printf(" Sourcegraph Cody, Oh My Pi (omp)\n"); + printf(" Sourcegraph Cody, Oh My Pi (omp), ZCode\n"); printf(" Conditional/explicit targets are changed only when their documented\n"); printf(" platform, marker, or explicit existing config path is present.\n"); printf(" Manual/UI MCP boundaries: Qodo, Warp, JetBrains AI/ACP, Replit,\n"); diff --git a/tests/test_agent_clients.c b/tests/test_agent_clients.c index f1bc3699e1..0cf31de7ec 100644 --- a/tests/test_agent_clients.c +++ b/tests/test_agent_clients.c @@ -159,10 +159,10 @@ static char *agent_deep_same_name_json(size_t depth) { TEST(agent_clients_registry_is_stable_and_callback_driven) { static const char *expected[] = { - "qoder", "kimi", "gitlab-duo", "rovo-dev", "amp", "devin", - "tabnine", "continue", "visual-studio", "trae", "roo-code", "amazon-q", - "codebuddy", "ibm-bob-ide", "ibm-bob-shell", "pochi", "pi", "sourcegraph-cody", - "omp", + "qoder", "kimi", "gitlab-duo", "rovo-dev", "amp", + "devin", "tabnine", "continue", "visual-studio", "trae", + "roo-code", "amazon-q", "codebuddy", "ibm-bob-ide", "ibm-bob-shell", + "pochi", "pi", "sourcegraph-cody", "omp", "zcode", }; static const uint32_t expected_capabilities[] = { CBM_AGENT_CAP_MCP | CBM_AGENT_CAP_SKILL | CBM_AGENT_CAP_AGENT | CBM_AGENT_CAP_HOOK, @@ -184,6 +184,7 @@ TEST(agent_clients_registry_is_stable_and_callback_driven) { CBM_AGENT_CAP_INSTRUCTIONS | CBM_AGENT_CAP_SKILL, CBM_AGENT_CAP_MCP, CBM_AGENT_CAP_MCP | CBM_AGENT_CAP_SKILL | CBM_AGENT_CAP_AGENT, + CBM_AGENT_CAP_MCP | CBM_AGENT_CAP_INSTRUCTIONS | CBM_AGENT_CAP_SKILL, }; ASSERT_EQ(cbm_agent_client_count(), CBM_AGENT_CLIENT_COUNT); ASSERT_EQ(CBM_AGENT_CLIENT_COUNT, sizeof(expected) / sizeof(expected[0])); @@ -343,6 +344,9 @@ TEST(agent_clients_resolve_documented_paths_and_precedence) { ASSERT_EQ(cbm_agent_client_resolve_path(CBM_AGENT_CLIENT_DEVIN, &options, path, sizeof(path)), 0); ASSERT_STR_EQ(path, "/home/tester/.config/devin/config.json"); + ASSERT_EQ(cbm_agent_client_resolve_path(CBM_AGENT_CLIENT_ZCODE, &options, path, sizeof(path)), + 0); + ASSERT_STR_EQ(path, "/home/tester/.zcode/cli/config.json"); options.is_windows = true; options.appdata_dir = "/roaming"; @@ -553,6 +557,12 @@ TEST(agent_clients_detect_installed_client_directories_before_mcp_exists) { probe.paths[0] = "/home/tester/.pi/agent"; probe.path_count = 1U; ASSERT(cbm_agent_client_detect(CBM_AGENT_CLIENT_PI, &options)); + + probe.path_count = 0U; + ASSERT(!cbm_agent_client_detect(CBM_AGENT_CLIENT_ZCODE, &options)); + probe.paths[0] = "/home/tester/.zcode"; + probe.path_count = 1U; + ASSERT(cbm_agent_client_detect(CBM_AGENT_CLIENT_ZCODE, &options)); PASS(); } @@ -833,6 +843,7 @@ TEST(agent_clients_json_schemas_are_exact_and_policy_neutral) { {CBM_AGENT_CLIENT_IBM_BOB_SHELL, "\"mcpServers\""}, {CBM_AGENT_CLIENT_POCHI, "\"mcp\""}, {CBM_AGENT_CLIENT_OMP, "\"type\": \"stdio\""}, + {CBM_AGENT_CLIENT_ZCODE, "\"servers\""}, }; const char *binary = "/opt/Codebase Memory/bin/cbm\\\"special"; for (size_t i = 0U; i < sizeof(cases) / sizeof(cases[0]); i++) { @@ -866,19 +877,19 @@ TEST(agent_clients_json_schemas_are_exact_and_policy_neutral) { TEST(agent_clients_new_standard_json_profiles_preserve_foreign_entries) { static const cbm_agent_client_id_t clients[] = { - CBM_AGENT_CLIENT_CODEBUDDY, - CBM_AGENT_CLIENT_IBM_BOB_IDE, - CBM_AGENT_CLIENT_IBM_BOB_SHELL, - CBM_AGENT_CLIENT_POCHI, - CBM_AGENT_CLIENT_OMP, + CBM_AGENT_CLIENT_CODEBUDDY, CBM_AGENT_CLIENT_IBM_BOB_IDE, CBM_AGENT_CLIENT_IBM_BOB_SHELL, + CBM_AGENT_CLIENT_POCHI, CBM_AGENT_CLIENT_OMP, CBM_AGENT_CLIENT_ZCODE, }; for (size_t i = 0U; i < sizeof(clients) / sizeof(clients[0]); i++) { const char *foreign = clients[i] == CBM_AGENT_CLIENT_POCHI ? "{\"mcp\":{\"codebase-memory-mcp\":{\"command\":\"foreign\"," "\"args\":[]}}}\n" - : "{\"mcpServers\":{\"codebase-memory-mcp\":{\"command\":\"foreign\"," - "\"args\":[]}}}\n"; + : (clients[i] == CBM_AGENT_CLIENT_ZCODE + ? "{\"mcp\":{\"servers\":{\"codebase-memory-mcp\":" + "{\"command\":\"foreign\",\"args\":[]}}}}\n" + : "{\"mcpServers\":{\"codebase-memory-mcp\":{\"command\":\"foreign\"," + "\"args\":[]}}}\n"); char *dir = NULL; char *path = agent_fixture(foreign, &dir); ASSERT_NOT_NULL(path); @@ -894,6 +905,67 @@ TEST(agent_clients_new_standard_json_profiles_preserve_foreign_entries) { PASS(); } +TEST(agent_clients_zcode_nested_mcp_servers_lifecycle) { + /* Fresh install creates the documented two-level mcp.servers path. */ + char *dir = NULL; + char *path = agent_fixture(NULL, &dir); + ASSERT_NOT_NULL(path); + ASSERT_EQ(cbm_agent_client_install_mcp(CBM_AGENT_CLIENT_ZCODE, path, "/usr/bin/cbm"), + CBM_AGENT_EDIT_OK); + char *installed = agent_read(path); + ASSERT_NOT_NULL(installed); + ASSERT_NOT_NULL(strstr(installed, "\"mcp\"")); + ASSERT_NOT_NULL(strstr(installed, "\"servers\"")); + ASSERT_NOT_NULL(strstr(installed, "\"codebase-memory-mcp\"")); + ASSERT_NOT_NULL(strstr(installed, "\"type\": \"stdio\"")); + ASSERT_NOT_NULL(strstr(installed, "\"command\": \"/usr/bin/cbm\"")); + ASSERT_EQ(agent_occurrences(installed, "\"codebase-memory-mcp\""), 1U); + free(installed); + + /* Reinstall is idempotent. */ + ASSERT_EQ(cbm_agent_client_install_mcp(CBM_AGENT_CLIENT_ZCODE, path, "/usr/bin/cbm"), + CBM_AGENT_EDIT_OK); + installed = agent_read(path); + ASSERT_NOT_NULL(installed); + ASSERT_EQ(agent_occurrences(installed, "\"codebase-memory-mcp\""), 1U); + free(installed); + + /* Sibling user servers and same-name foreign entries are preserved. */ + const char *with_sibling = + "{\"mcp\":{\"servers\":{\"brave-search\":{\"type\":\"http\",\"url\":\"https://x\"}}}}\n"; + ASSERT_EQ(th_write_file(path, with_sibling), 0); + ASSERT_EQ(cbm_agent_client_install_mcp(CBM_AGENT_CLIENT_ZCODE, path, "/usr/bin/cbm"), + CBM_AGENT_EDIT_OK); + installed = agent_read(path); + ASSERT_NOT_NULL(installed); + ASSERT_NOT_NULL(strstr(installed, "\"brave-search\"")); + ASSERT_NOT_NULL(strstr(installed, "\"codebase-memory-mcp\"")); + free(installed); + ASSERT_EQ(cbm_agent_client_remove_mcp(CBM_AGENT_CLIENT_ZCODE, path, "/usr/bin/cbm"), + CBM_AGENT_EDIT_OK); + installed = agent_read(path); + ASSERT_NOT_NULL(installed); + ASSERT_NULL(strstr(installed, "\"codebase-memory-mcp\"")); + ASSERT_NOT_NULL(strstr(installed, "\"brave-search\"")); + free(installed); + ASSERT_EQ(cbm_agent_client_remove_mcp(CBM_AGENT_CLIENT_ZCODE, path, "/usr/bin/cbm"), + CBM_AGENT_EDIT_OK); + + const char *foreign = "{\"mcp\":{\"servers\":{\"codebase-memory-mcp\":{\"type\":\"stdio\"," + "\"command\":\"foreign\",\"args\":[]}}}}\n"; + ASSERT_EQ(th_write_file(path, foreign), 0); + ASSERT_EQ(cbm_agent_client_install_mcp(CBM_AGENT_CLIENT_ZCODE, path, "/usr/bin/cbm"), + CBM_AGENT_EDIT_FOREIGN); + ASSERT_EQ(cbm_agent_client_remove_mcp(CBM_AGENT_CLIENT_ZCODE, path, "/usr/bin/cbm"), + CBM_AGENT_EDIT_FOREIGN); + char *after = agent_read(path); + ASSERT_STR_EQ(after, foreign); + free(after); + free(path); + th_cleanup(dir); + PASS(); +} + TEST(agent_clients_refuse_foreign_and_preserve_modified_entries) { const char *foreign = "{\"mcpServers\":{\"codebase-memory-mcp\":{\"command\":\"foreign\",\"args\":[]}}}\n"; @@ -941,30 +1013,29 @@ TEST(agent_clients_omp_resolves_to_injected_agent_dir_when_provided) { char resolved[512]; /* Default (no env) keeps the documented ~/.omp/agent path. */ - ASSERT_EQ(cbm_agent_client_resolve_path(CBM_AGENT_CLIENT_OMP, &options, resolved, - sizeof(resolved)), - 0); + ASSERT_EQ( + cbm_agent_client_resolve_path(CBM_AGENT_CLIENT_OMP, &options, resolved, sizeof(resolved)), + 0); ASSERT_STR_EQ(resolved, "/home/tester/.omp/agent/mcp.json"); /* Named profile directory takes precedence over the home-relative default. */ options.omp_agent_dir = "/home/tester/.omp/profiles/work/agent"; - ASSERT_EQ(cbm_agent_client_resolve_path(CBM_AGENT_CLIENT_OMP, &options, resolved, - sizeof(resolved)), - 0); + ASSERT_EQ( + cbm_agent_client_resolve_path(CBM_AGENT_CLIENT_OMP, &options, resolved, sizeof(resolved)), + 0); ASSERT_STR_EQ(resolved, "/home/tester/.omp/profiles/work/agent/mcp.json"); /* PI_CODING_AGENT_DIR relocations also flow through the resolved option. */ options.omp_agent_dir = "/srv/omp-shared/agent"; - ASSERT_EQ(cbm_agent_client_resolve_path(CBM_AGENT_CLIENT_OMP, &options, resolved, - sizeof(resolved)), - 0); + ASSERT_EQ( + cbm_agent_client_resolve_path(CBM_AGENT_CLIENT_OMP, &options, resolved, sizeof(resolved)), + 0); ASSERT_STR_EQ(resolved, "/srv/omp-shared/agent/mcp.json"); PASS(); } TEST(agent_clients_omp_profile_does_not_register_global_instructions_capability) { - const cbm_agent_client_profile_t *profile = - cbm_agent_client_by_id(CBM_AGENT_CLIENT_OMP); + const cbm_agent_client_profile_t *profile = cbm_agent_client_by_id(CBM_AGENT_CLIENT_OMP); ASSERT_NOT_NULL(profile); ASSERT_EQ(profile->capabilities & CBM_AGENT_CAP_INSTRUCTIONS, 0U); ASSERT_NEQ(profile->capabilities & CBM_AGENT_CAP_MCP, 0U); @@ -973,7 +1044,6 @@ TEST(agent_clients_omp_profile_does_not_register_global_instructions_capability) PASS(); } - TEST(agent_clients_remove_only_canonical_and_missing_is_noop) { char *dir = NULL; char *path = agent_fixture("{\"keep\":true}\n", &dir); @@ -1391,6 +1461,7 @@ SUITE(agent_clients) { RUN_TEST(agent_clients_cody_uses_literal_dotted_key_without_feature_or_permission_edits); RUN_TEST(agent_clients_json_schemas_are_exact_and_policy_neutral); RUN_TEST(agent_clients_new_standard_json_profiles_preserve_foreign_entries); + RUN_TEST(agent_clients_zcode_nested_mcp_servers_lifecycle); RUN_TEST(agent_clients_refuse_foreign_and_preserve_modified_entries); RUN_TEST(agent_clients_omp_resolves_to_injected_agent_dir_when_provided); RUN_TEST(agent_clients_omp_profile_does_not_register_global_instructions_capability); diff --git a/tests/test_cli.c b/tests/test_cli.c index 539b0df183..c263d4ccfa 100644 --- a/tests/test_cli.c +++ b/tests/test_cli.c @@ -7216,14 +7216,15 @@ TEST(cli_supported_agent_surfaces_match_installers) { "Pi", "Sourcegraph Cody", "Oh My Pi (omp)", + "ZCode", }; - ASSERT_EQ(sizeof(required_agents) / sizeof(required_agents[0]), 45U); + ASSERT_EQ(sizeof(required_agents) / sizeof(required_agents[0]), 46U); char *data = read_test_file_alloc("README.md"); if (!data) FAIL("could not read README.md for supported-agent contract"); - if (!strstr(data, "45 supported automatic/conditional client surfaces")) { + if (!strstr(data, "46 supported automatic/conditional client surfaces")) { free(data); - FAIL("README must describe all 45 automatic/conditional client surfaces accurately"); + FAIL("README must describe all 46 automatic/conditional client surfaces accurately"); } for (size_t i = 0; i < sizeof(required_agents) / sizeof(required_agents[0]); i++) { if (!strstr(data, required_agents[i])) { @@ -7236,9 +7237,9 @@ TEST(cli_supported_agent_surfaces_match_installers) { data = read_test_file_alloc("pkg/npm/README.md"); if (!data) FAIL("could not read npm README for supported-agent contract"); - if (!strstr(data, "45 supported automatic/conditional client surfaces")) { + if (!strstr(data, "46 supported automatic/conditional client surfaces")) { free(data); - FAIL("npm README must describe all 45 automatic/conditional client surfaces accurately"); + FAIL("npm README must describe all 46 automatic/conditional client surfaces accurately"); } for (size_t i = 0; i < sizeof(required_agents) / sizeof(required_agents[0]); i++) { if (!strstr(data, required_agents[i])) { @@ -7251,9 +7252,9 @@ TEST(cli_supported_agent_surfaces_match_installers) { data = read_test_file_alloc("docs/index.html"); if (!data) FAIL("could not read docs/index.html for supported-agent contract"); - if (!strstr(data, "configures 45 automatic/conditional client surfaces")) { + if (!strstr(data, "configures 46 automatic/conditional client surfaces")) { free(data); - FAIL("landing page must describe all 45 automatic/conditional client surfaces accurately"); + FAIL("landing page must describe all 46 automatic/conditional client surfaces accurately"); } for (size_t i = 0; i < sizeof(required_agents) / sizeof(required_agents[0]); i++) { if (!strstr(data, required_agents[i])) { @@ -7272,7 +7273,7 @@ TEST(cli_supported_agent_surfaces_match_installers) { FAIL("CLI help must list every automatic/conditional client surface"); } } - if (!strstr(data, "Supported automatic/conditional client surfaces (45)")) { + if (!strstr(data, "Supported automatic/conditional client surfaces (46)")) { free(data); FAIL("CLI help must not describe all conditional surfaces as auto-detected"); } @@ -7281,10 +7282,10 @@ TEST(cli_supported_agent_surfaces_match_installers) { data = read_test_file_alloc("docs/llms.txt"); if (!data) FAIL("could not read docs/llms.txt for supported-agent contract"); - if (!strstr(data, "45 automatic/conditional client surfaces") || - !strstr(data, "39 automatically detected") || !strstr(data, "6 conditional/explicit")) { + if (!strstr(data, "46 automatic/conditional client surfaces") || + !strstr(data, "40 automatically detected") || !strstr(data, "6 conditional/explicit")) { free(data); - FAIL("llms.txt must describe the 45-surface 39+6 support matrix accurately"); + FAIL("llms.txt must describe the 46-surface 40+6 support matrix accurately"); } for (size_t i = 0; i < sizeof(required_agents) / sizeof(required_agents[0]); i++) { if (!strstr(data, required_agents[i])) {