diff --git a/README.md b/README.md index 46fb6317b8..e09c04c3c0 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![MCP tools](https://img.shields.io/badge/MCP_tools-17-blue)](#mcp-tools) [![Languages](https://img.shields.io/badge/languages-162-orange)](https://github.com/DeusData/codebase-memory-mcp) [![Hybrid LSP](https://img.shields.io/badge/Hybrid_LSP-type_resolution-blue)](#hybrid-lsp) -[![Agents](https://img.shields.io/badge/agent_surfaces-45-purple)](https://github.com/DeusData/codebase-memory-mcp) +[![Agents](https://img.shields.io/badge/agent_surfaces-46-purple)](https://github.com/DeusData/codebase-memory-mcp) [![Pure C](https://img.shields.io/badge/pure_C-no_language_runtime-blue)](https://github.com/DeusData/codebase-memory-mcp) [![Platform](https://img.shields.io/badge/macOS_%7C_Linux_%7C_Windows-supported-lightgrey)](https://github.com/DeusData/codebase-memory-mcp/releases/latest) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/DeusData/codebase-memory-mcp/badge)](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) @@ -699,7 +699,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, @@ -766,6 +766,7 @@ overwrite user-modified agents. | Pi | Detected | none | `~/.pi/agent/AGENTS.md` + skill; MCP/subagents require an explicit reviewed extension | | 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` | +| agentty | Detected | `~/.agentty/mcp.json` (overridden by `$AGENTTY_MCP_CONFIG`) | Skill in `~/.agentty/skills` (Claude-compatible SKILL.md) | | Sourcegraph Cody | Explicit opt-in | Existing `$CBM_CODY_CONFIG_PATH` | MCP only | For Codex, install keeps only a tiny managed activation pointer in global @@ -1145,7 +1146,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..c83029c23a 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 @@

Understand the
whole repository.

One connected graphSource, docs and infrastructure
17 MCP toolsPlus native CLI access
-
45 client surfacesAutomatic or conditional setup
+
46 client surfacesAutomatic or conditional setup
Local processingYour index stays on your machine
@@ -426,14 +426,14 @@

Understand the
whole repository.

-

Agent setup

Context that fits
your coding workflow.

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

Context that fits
your coding workflow.

The installer configures 46 automatic/conditional client surfaces. MCP connections, instructions, skills, profiles and hooks are installed according to each client's supported capabilities.

01 / CONNECTSet up your clients

Configure supported integrations for clients such as Claude Code, Codex CLI, Gemini CLI, Cursor, Zed and OpenCode.

02 / GUIDEGive agents a working method

Skills and instructions guide graph queries, coverage checks and source verification. Supported custom-agent formats get Scout, Verify and Auditor profiles.

03 / MAINTAIN CONTEXTUse supported hooks

Client-specific lifecycle and read/search hooks help surface relevant graph context. Hook and subagent capabilities differ by client and platform.

See the supported clients and integration boundaries -

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 agentty.

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.

Review the client matrix and setup details →

diff --git a/docs/llms.txt b/docs/llms.txt index 83829939fc..1f8d00e547 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), agentty, and Sourcegraph Cody. ## Measurement and limits diff --git a/pkg/npm/README.md b/pkg/npm/README.md index bce4d52177..0334ac9bdc 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 agentty. - **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..cb0230ecdf 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_AGENTTY, "agentty", "agentty", CBM_AGENT_STABLE, + CBM_AGENT_CAP_MCP | CBM_AGENT_CAP_SKILL, "agentty", agent_install_callback, + agent_remove_callback}, }; size_t cbm_agent_client_count(void) { @@ -601,6 +604,14 @@ int cbm_agent_client_resolve_path(cbm_agent_client_id_t id, int written = snprintf(path_out, path_out_size, "%s", options->cody_config_path); return written >= 0 && (size_t)written < path_out_size ? 0 : -1; } + case CBM_AGENT_CLIENT_AGENTTY: + /* $AGENTTY_MCP_CONFIG names the one file agentty reads instead of + * ~/.agentty/mcp.json, so install and uninstall must target it. */ + if (options->agentty_mcp_config && options->agentty_mcp_config[0]) { + int written = snprintf(path_out, path_out_size, "%s", options->agentty_mcp_config); + return written >= 0 && (size_t)written < path_out_size ? 0 : -1; + } + return agent_join_path(path_out, path_out_size, options->home_dir, ".agentty/mcp.json"); default: return -1; } @@ -663,6 +674,8 @@ static int agent_client_marker_path(cbm_agent_client_id_t id, return agent_join_path(path_out, path_out_size, options->home_dir, ".omp/agent"); case CBM_AGENT_CLIENT_PI: return agent_join_path(path_out, path_out_size, options->home_dir, ".pi/agent"); + case CBM_AGENT_CLIENT_AGENTTY: + return agent_join_path(path_out, path_out_size, options->home_dir, ".agentty"); default: return 1; } @@ -1466,6 +1479,7 @@ static bool agent_json_client(cbm_agent_client_id_t id) { case CBM_AGENT_CLIENT_IBM_BOB_SHELL: case CBM_AGENT_CLIENT_POCHI: case CBM_AGENT_CLIENT_SOURCEGRAPH_CODY: + case CBM_AGENT_CLIENT_AGENTTY: return true; case CBM_AGENT_CLIENT_CONTINUE: case CBM_AGENT_CLIENT_PI: diff --git a/src/cli/agent_clients.h b/src/cli/agent_clients.h index ccf9f81a16..093abe7419 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_AGENTTY, CBM_AGENT_CLIENT_COUNT } cbm_agent_client_id_t; @@ -77,6 +78,8 @@ typedef struct { const char *roo_config_path; const char *cody_config_path; const char *omp_agent_dir; + /* $AGENTTY_MCP_CONFIG, already home-expanded by the caller; NULL = default. */ + const char *agentty_mcp_config; bool is_windows; cbm_agent_probe_fn path_exists; cbm_agent_probe_fn command_exists; diff --git a/src/cli/cli.c b/src/cli/cli.c index 22f6ae7a10..60f10f2233 100644 --- a/src/cli/cli.c +++ b/src/cli/cli.c @@ -9912,6 +9912,7 @@ typedef struct { char roo_config_path[CLI_BUF_1K]; char cody_config_path[CLI_BUF_1K]; char omp_agent_dir[CLI_BUF_1K]; + char agentty_mcp_config[CLI_BUF_1K]; } cbm_agent_registry_context_t; static const char *cbm_agent_registry_env_path(const char *env_name, const char *home, @@ -9966,6 +9967,9 @@ static void cbm_init_agent_registry_context(const char *home, if (cbm_omp_agent_dir(home, registry->omp_agent_dir, sizeof(registry->omp_agent_dir))) { registry->options.omp_agent_dir = registry->omp_agent_dir; } + registry->options.agentty_mcp_config = + cbm_agent_registry_env_path("AGENTTY_MCP_CONFIG", home, registry->agentty_mcp_config, + sizeof(registry->agentty_mcp_config)); #ifdef _WIN32 registry->options.is_windows = true; #else @@ -10466,6 +10470,10 @@ 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_AGENTTY) { + char skills_dir[CLI_BUF_1K]; + snprintf(skills_dir, sizeof(skills_dir), "%s/.agentty/skills", home); + install_agent_skill(profile->display_name, skills_dir, force, dry_run); } } } @@ -11637,6 +11645,10 @@ const char *cbm_cli_clients_token_for_testing(size_t index) { void cbm_cli_set_client_selection_for_testing(const char *spec) { g_client_selection = spec; } +bool cbm_cli_selects_registry_client_for_testing(const char *stable_id) { + const cbm_agent_client_profile_t *profile = cbm_agent_client_by_stable_id(stable_id); + return profile && cli_clients_selects_registry_client(g_client_selection, profile->id); +} #endif /* ── Subcommand: install ──────────────────────────────────────── */ @@ -13043,6 +13055,10 @@ 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_AGENTTY) { + char skills_dir[CLI_BUF_1K]; + snprintf(skills_dir, sizeof(skills_dir), "%s/.agentty/skills", home); + uninstall_agent_skill(profile->display_name, skills_dir, dry_run); } } } diff --git a/src/cli/cli.h b/src/cli/cli.h index 7d0c4e8da9..a10651870e 100644 --- a/src/cli/cli.h +++ b/src/cli/cli.h @@ -296,6 +296,9 @@ bool cbm_cli_clients_apply_selection_for_testing(const char *spec, cbm_detected_ size_t cbm_cli_clients_count_for_testing(void); const char *cbm_cli_clients_token_for_testing(size_t index); void cbm_cli_set_client_selection_for_testing(const char *spec); +/* True when the current --clients selection includes the registry client id + * (registry-backed path of the same selector). */ +bool cbm_cli_selects_registry_client_for_testing(const char *stable_id); #endif #ifdef CBM_CLI_ENABLE_TEST_API diff --git a/src/main.c b/src/main.c index e5cb74c340..b1e2475368 100644 --- a/src/main.c +++ b/src/main.c @@ -1165,7 +1165,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"); @@ -1174,7 +1174,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), agentty\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 c92005f9fb..4b58b2fd4e 100644 --- a/tests/test_agent_clients.c +++ b/tests/test_agent_clients.c @@ -162,7 +162,7 @@ TEST(agent_clients_registry_is_stable_and_callback_driven) { "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", + "omp", "agentty", }; 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_SKILL, }; ASSERT_EQ(cbm_agent_client_count(), CBM_AGENT_CLIENT_COUNT); ASSERT_EQ(CBM_AGENT_CLIENT_COUNT, sizeof(expected) / sizeof(expected[0])); diff --git a/tests/test_cli.c b/tests/test_cli.c index 15255ff84f..c66dbe232f 100644 --- a/tests/test_cli.c +++ b/tests/test_cli.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -7454,6 +7455,101 @@ TEST(cli_detect_agents_finds_cursor_issue222) { PASS(); } +TEST(cli_detect_agents_finds_agentty) { + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + FAIL("cbm_mkdtemp failed"); + + /* agentty lives in the table-driven registry, not the legacy detector. */ + const cbm_agent_client_profile_t *profile = cbm_agent_client_by_stable_id("agentty"); + ASSERT_NOT_NULL(profile); + + char dir[512]; + snprintf(dir, sizeof(dir), "%s/.agentty", tmpdir); + test_mkdirp(dir); + + cbm_agent_client_resolve_options_t options; + memset(&options, 0, sizeof(options)); + options.home_dir = tmpdir; + ASSERT_TRUE(cbm_agent_client_detect(CBM_AGENT_CLIENT_AGENTTY, &options)); + + char config_path[512]; + ASSERT_EQ(cbm_agent_client_resolve_path(CBM_AGENT_CLIENT_AGENTTY, &options, config_path, + sizeof(config_path)), + 0); + char expected[512]; + snprintf(expected, sizeof(expected), "%s/.agentty/mcp.json", tmpdir); + ASSERT_STR_EQ(config_path, expected); + + test_rmdir_r(tmpdir); + PASS(); +} + +TEST(cli_agentty_mcp_config_env_override) { + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-agentty-env-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + FAIL("cbm_mkdtemp failed"); + + char agentty_dir[512]; + snprintf(agentty_dir, sizeof(agentty_dir), "%s/.agentty", tmpdir); + test_mkdirp(agentty_dir); + char *saved = save_test_env("AGENTTY_MCP_CONFIG"); + char override_path[512]; + snprintf(override_path, sizeof(override_path), "%s/override.json", tmpdir); + char default_path[512]; + snprintf(default_path, sizeof(default_path), "%s/.agentty/mcp.json", tmpdir); + + /* The registry option is the only input the resolver reads. */ + cbm_agent_client_resolve_options_t options; + memset(&options, 0, sizeof(options)); + options.home_dir = tmpdir; + options.agentty_mcp_config = override_path; + char config_path[512]; + ASSERT_EQ(cbm_agent_client_resolve_path(CBM_AGENT_CLIENT_AGENTTY, &options, config_path, + sizeof(config_path)), + 0); + ASSERT_STR_EQ(config_path, override_path); + options.agentty_mcp_config = NULL; + ASSERT_EQ(cbm_agent_client_resolve_path(CBM_AGENT_CLIENT_AGENTTY, &options, config_path, + sizeof(config_path)), + 0); + ASSERT_STR_EQ(config_path, default_path); + + /* The install plan reads $AGENTTY_MCP_CONFIG through the same registry + * env path as every other client override: an absolute or ~ value is + * honored, a relative one is ignored instead of landing in whatever + * directory install happens to run from. */ + cbm_setenv("AGENTTY_MCP_CONFIG", override_path, 1); + char *json = cbm_build_install_plan_json(tmpdir, "/usr/local/bin/codebase-memory-mcp"); + bool absolute_honored = json && strstr(json, override_path) != NULL; + free(json); + + cbm_setenv("AGENTTY_MCP_CONFIG", "~/agentty-override.json", 1); + char tilde_path[512]; + snprintf(tilde_path, sizeof(tilde_path), "%s/agentty-override.json", tmpdir); + json = cbm_build_install_plan_json(tmpdir, "/usr/local/bin/codebase-memory-mcp"); + bool tilde_honored = json && strstr(json, tilde_path) != NULL; + free(json); + + cbm_setenv("AGENTTY_MCP_CONFIG", "agentty-relative.json", 1); + json = cbm_build_install_plan_json(tmpdir, "/usr/local/bin/codebase-memory-mcp"); + bool relative_ignored = + json && strstr(json, default_path) != NULL && strstr(json, "agentty-relative.json") == NULL; + free(json); + + restore_test_env("AGENTTY_MCP_CONFIG", saved); + test_rmdir_r(tmpdir); + if (!absolute_honored) + FAIL("install plan must target an absolute $AGENTTY_MCP_CONFIG"); + if (!tilde_honored) + FAIL("install plan must expand a leading ~ in $AGENTTY_MCP_CONFIG"); + if (!relative_ignored) + FAIL("install plan must ignore a relative $AGENTTY_MCP_CONFIG"); + PASS(); +} + /* issue #388: `install --plan` must emit a machine-readable receipt of planned * writes WITHOUT mutating any config (the pre-mutation trust primitive). */ TEST(cli_install_plan_receipt_no_mutation_issue388) { @@ -7571,14 +7667,18 @@ TEST(cli_supported_agent_surfaces_match_installers) { "Pi", "Sourcegraph Cody", "Oh My Pi (omp)", + "agentty", }; - ASSERT_EQ(sizeof(required_agents) / sizeof(required_agents[0]), 45U); + ASSERT_EQ(sizeof(required_agents) / sizeof(required_agents[0]), 46U); + /* The table-driven registry must carry agentty too — the legacy detector no + * longer knows about it. */ + ASSERT_NOT_NULL(cbm_agent_client_by_stable_id("agentty")); 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])) { @@ -7591,9 +7691,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])) { @@ -7606,9 +7706,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])) { @@ -7627,7 +7727,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"); } @@ -7636,10 +7736,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])) { @@ -7687,6 +7787,7 @@ TEST(cli_new_agent_install_plans_use_documented_paths) { #endif ".vibe", ".grok", + ".agentty", }; char path[768]; for (size_t i = 0; i < sizeof(dirs) / sizeof(dirs[0]); i++) { @@ -7737,6 +7838,9 @@ TEST(cli_new_agent_install_plans_use_documented_paths) { "/.grok/rules/codebase-memory.md", "/.grok/skills/codebase-memory/SKILL.md", "/.grok/agents/codebase-memory.md", + "\"agentty\"", + "/.agentty/mcp.json", + "/.agentty/skills/codebase-memory/SKILL.md", }; const char *missing = NULL; for (size_t i = 0; json && i < sizeof(expected) / sizeof(expected[0]); i++) { @@ -7796,6 +7900,7 @@ TEST(cli_new_agent_configs_use_documented_schemas) { #endif ".vibe", ".grok", + ".agentty", }; char path[768]; for (size_t i = 0; i < sizeof(dirs) / sizeof(dirs[0]); i++) { @@ -7904,6 +8009,13 @@ TEST(cli_new_agent_configs_use_documented_schemas) { snprintf(path, sizeof(path), "%s/.grok/rules/codebase-memory.md", tmpdir); schemas_ok = schemas_ok && test_file_contains_all(path, durable_hint, 3); + /* agentty consumes the standard mcpServers shape and Claude-compatible + * SKILL.md — same fixtures as OpenHands/Cline. */ + snprintf(path, sizeof(path), "%s/.agentty/mcp.json", tmpdir); + schemas_ok = schemas_ok && test_file_contains_all(path, standard_json, 3); + snprintf(path, sizeof(path), "%s/.agentty/skills/codebase-memory/SKILL.md", tmpdir); + schemas_ok = schemas_ok && test_file_contains_all(path, shared_skill, 3); + for (size_t i = 0; i < sizeof(env_names) / sizeof(env_names[0]); i++) { restore_test_env(env_names[i], saved_env[i]); } @@ -7913,6 +8025,124 @@ TEST(cli_new_agent_configs_use_documented_schemas) { PASS(); } +TEST(cli_agentty_install_then_uninstall_roundtrip) { + char tmpdir[256]; + snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-agentty-XXXXXX"); + if (!cbm_mkdtemp(tmpdir)) + FAIL("cbm_mkdtemp failed"); + + char *saved_home = save_test_env("HOME"); + char *saved_path = save_test_env("PATH"); + char *saved_cache = save_test_env("CBM_CACHE_DIR"); + char *saved_override = save_test_env("AGENTTY_MCP_CONFIG"); + cbm_setenv("HOME", tmpdir, 1); + cbm_setenv("PATH", tmpdir, 1); + cbm_unsetenv("CBM_CACHE_DIR"); + + char agentty_dir[512]; + char mcp_path[512]; + char skills_dir[512]; + char skill_path[640]; + char user_dir[512]; + char user_file[640]; + char binary[512]; + snprintf(agentty_dir, sizeof(agentty_dir), "%s/.agentty", tmpdir); + snprintf(mcp_path, sizeof(mcp_path), "%s/mcp.json", agentty_dir); + snprintf(skills_dir, sizeof(skills_dir), "%s/skills", agentty_dir); + snprintf(skill_path, sizeof(skill_path), "%s/codebase-memory/SKILL.md", skills_dir); + snprintf(user_dir, sizeof(user_dir), "%s/keepme", skills_dir); + snprintf(user_file, sizeof(user_file), "%s/SKILL.md", user_dir); +#ifdef _WIN32 + snprintf(binary, sizeof(binary), "%s/.local/bin/codebase-memory-mcp.exe", tmpdir); +#else + snprintf(binary, sizeof(binary), "%s/.local/bin/codebase-memory-mcp", tmpdir); +#endif + + test_mkdirp(agentty_dir); + test_mkdirp(user_dir); + write_test_file(user_file, "user-owned skill\n"); + write_test_file(binary, ""); + /* Seed a foreign server so install/uninstall must preserve it. */ + write_test_file(mcp_path, "{\"mcpServers\":{\"other-mcp\":{\"command\":\"/opt/other\"}}}\n"); + + cbm_install_agent_configs(tmpdir, binary, false, false); + + struct stat state; + const char *const installed_markers[] = {"mcpServers", "codebase-memory-mcp", binary}; + bool installed = test_file_contains_all(mcp_path, installed_markers, 3) && + stat(skill_path, &state) == 0; + char *mcp_after_install = read_test_file_alloc(mcp_path); + bool foreign_survives_install = mcp_after_install && + strstr(mcp_after_install, "\"other-mcp\"") != NULL; + + char *argv[] = {"uninstall", "--yes"}; + int uninstall_rc = cli_test_cmd_uninstall(2, argv); + + bool skill_removed = stat(skill_path, &state) != 0; + char *mcp_after_uninstall = read_test_file_alloc(mcp_path); + bool mcp_entry_removed = + mcp_after_uninstall && strstr(mcp_after_uninstall, "codebase-memory-mcp") == NULL; + bool user_kept = stat(user_file, &state) == 0; + bool foreign_kept = mcp_after_uninstall && strstr(mcp_after_uninstall, "\"other-mcp\"") != NULL; + + /* $AGENTTY_MCP_CONFIG redirects both install and uninstall: agentty never + * reads ~/.agentty/mcp.json while the variable points at an explicit + * file, so writing there would silently no-op on the agent's side. */ + char override_path[640]; + snprintf(override_path, sizeof(override_path), "%s/override-mcp.json", tmpdir); + cbm_setenv("AGENTTY_MCP_CONFIG", override_path, 1); + cbm_install_agent_configs(tmpdir, binary, false, false); + struct stat override_state; + const char *const override_markers[] = {"mcpServers", "codebase-memory-mcp", binary}; + bool override_written = test_file_contains_all(override_path, override_markers, 3) && + stat(override_path, &override_state) == 0; + char *default_after_override = read_test_file_alloc(mcp_path); + bool default_untouched = + !default_after_override || strstr(default_after_override, "codebase-memory-mcp") == NULL; + free(default_after_override); + argv[0] = "uninstall"; + int override_uninstall_rc = cli_test_cmd_uninstall(2, argv); + char *override_after_uninstall = read_test_file_alloc(override_path); + bool override_entry_removed = + override_after_uninstall && strstr(override_after_uninstall, "codebase-memory-mcp") == NULL; + free(override_after_uninstall); + + /* Restore HOME/PATH before any FAIL: a failure here must not leave the + * rest of the suite running without git on PATH. */ + free(mcp_after_install); + free(mcp_after_uninstall); + restore_test_env("HOME", saved_home); + restore_test_env("PATH", saved_path); + restore_test_env("CBM_CACHE_DIR", saved_cache); + restore_test_env("AGENTTY_MCP_CONFIG", saved_override); + test_rmdir_r(tmpdir); + + if (!override_written) + FAIL("install must honor $AGENTTY_MCP_CONFIG and write the override file"); + if (!default_untouched) + FAIL("install must not re-add the entry to ~/.agentty/mcp.json while " + "$AGENTTY_MCP_CONFIG is set"); + if (!override_entry_removed) + FAIL("uninstall must honor $AGENTTY_MCP_CONFIG and clean the override file"); + if (override_uninstall_rc != 0) + FAIL("uninstall must exit successfully when $AGENTTY_MCP_CONFIG is set"); + if (!installed) + FAIL("install must register the codebase-memory server and skill for agentty"); + if (!foreign_survives_install) + FAIL("install must preserve a pre-existing foreign server in ~/.agentty/mcp.json"); + if (!skill_removed) + FAIL("uninstall must remove the codebase-memory skill directory"); + if (!mcp_entry_removed) + FAIL("uninstall must remove the owned codebase-memory MCP entry"); + if (!user_kept) + FAIL("uninstall must not touch user-owned skills in ~/.agentty/skills/"); + if (!foreign_kept) + FAIL("uninstall must preserve foreign servers in ~/.agentty/mcp.json"); + if (uninstall_rc != 0) + FAIL("uninstall must exit successfully for the agentty roundtrip"); + PASS(); +} + TEST(cli_agent_reinstall_preserves_foreign_policy_entries) { char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-agent-policy-XXXXXX"); @@ -19798,6 +20028,19 @@ TEST(cli_clients_selector_vocabulary_is_complete_and_strict_issue1558) { ASSERT_FALSE(registry.claude_code); ASSERT_FALSE(registry.cursor); + /* agentty is a registry client: selectable through the shared selector + * vocabulary, and a selection that omits it must drop it (a registry + * install only ever runs when cli_clients_selects_registry_client passes). */ + cbm_detected_agents_t agentty = all; + ASSERT_TRUE(cbm_cli_clients_apply_selection_for_testing("agentty", &agentty)); + cbm_detected_agents_t leak = all; + ASSERT_TRUE(cbm_cli_clients_apply_selection_for_testing("claude,codex", &leak)); + cbm_cli_set_client_selection_for_testing("agentty"); + ASSERT_TRUE(cbm_cli_selects_registry_client_for_testing("agentty")); + cbm_cli_set_client_selection_for_testing("claude,codex"); + ASSERT_FALSE(cbm_cli_selects_registry_client_for_testing("agentty")); + cbm_cli_set_client_selection_for_testing(NULL); + /* Every token in the table must resolve — a client added to detection but * forgotten here is invisible to the selector. */ for (size_t i = 0; i < cbm_cli_clients_count_for_testing(); i++) { @@ -20655,7 +20898,7 @@ SUITE(cli) { RUN_TEST(cli_yaml_parse_empty); RUN_TEST(cli_yaml_has); - /* Agent detection (6 tests — group A) */ + /* Agent detection */ RUN_TEST(cli_detect_agents_finds_claude); RUN_TEST(cli_detect_agents_finds_claude_via_env); RUN_TEST(cli_detect_claude_empty_dir_not_detected_issue1180); @@ -20668,6 +20911,9 @@ SUITE(cli) { RUN_TEST(cli_detect_agents_finds_codex); RUN_TEST(cli_detect_agents_finds_grok); RUN_TEST(cli_detect_agents_finds_cursor_issue222); + RUN_TEST(cli_detect_agents_finds_agentty); + RUN_TEST(cli_agentty_mcp_config_env_override); + RUN_TEST(cli_agentty_install_then_uninstall_roundtrip); RUN_TEST(cli_install_plan_receipt_no_mutation_issue388); RUN_TEST(cli_supported_agent_surfaces_match_installers); RUN_TEST(cli_new_agent_install_plans_use_documented_paths);