diff --git a/README.md b/README.md index da4d824e0..d52dc5e30 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Tests](https://img.shields.io/badge/tests-8050_passing-brightgreen)](https://github.com/DeusData/codebase-memory-mcp) [![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-10_languages-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) @@ -16,7 +16,7 @@ **The fastest and most efficient code intelligence engine for AI coding agents.** Full-indexes an average repository in milliseconds, the Linux kernel (28M LOC, 75K files) in 3 minutes. Answers structural queries in under 1ms. Ships as a native executable with a small verified runtime-asset set for macOS, Linux, and Windows — download, run `install`, done. -High-quality parsing through [tree-sitter](https://tree-sitter.github.io/tree-sitter/) AST analysis across all 162 languages, enhanced with [**Hybrid LSP** semantic type resolution](#hybrid-lsp) for Python, TypeScript / JavaScript / JSX / TSX, PHP, C#, Go, C, C++, Java, Kotlin, Rust, and Perl — producing a persistent knowledge graph of functions, classes, call chains, HTTP routes, and cross-service links. 17 MCP tools. No language runtime, hosted service, or API key. Plug and play across 45 supported automatic/conditional client surfaces. +High-quality parsing through [tree-sitter](https://tree-sitter.github.io/tree-sitter/) AST analysis across all 162 languages, enhanced with [**Hybrid LSP** semantic type resolution](#hybrid-lsp) for Python, TypeScript / JavaScript / JSX / TSX, PHP, C#, Go, C, C++, Java, Kotlin, Rust, and Perl — producing a persistent knowledge graph of functions, classes, call chains, HTTP routes, and cross-service links. 17 MCP tools. No language runtime, hosted service, or API key. Plug and play across 46 supported automatic/conditional client surfaces. > **Research** — The design and benchmarks behind this project are described in the preprint [*Codebase-Memory: Tree-Sitter-Based Knowledge Graphs for LLM Code Exploration via MCP*](https://arxiv.org/abs/2603.27277) (arXiv:2603.27277). Evaluated across 31 real-world repositories: 83% answer quality, 10× fewer tokens, 2.1× fewer tool calls vs. file-by-file exploration. @@ -34,7 +34,7 @@ High-quality parsing through [tree-sitter](https://tree-sitter.github.io/tree-si - **Plug and play** — native executable plus authenticated release-owned assets for macOS (arm64/amd64), Linux (arm64/amd64), and Windows (amd64). The native install needs no Docker, language runtime, or API keys. Download → `install` → restart agent → done. - **162 languages** — vendored tree-sitter grammars compiled into the binary. Nothing to install, nothing that breaks. - **120x fewer tokens** — 5 structural queries: ~3,400 tokens vs ~412,000 via file-by-file search. One graph query replaces dozens of grep/read cycles. -- **45 supported automatic/conditional client surfaces** — `install` configures detected clients and safely activates conditional clients only when their documented platform, marker, or explicit existing config path is present. See [Multi-Agent Support](#multi-agent-support) for the complete matrix and manual/UI-only boundaries. +- **46 supported automatic/conditional client surfaces** — `install` configures detected clients and safely activates conditional clients only when their documented platform, marker, or explicit existing config path is present. See [Multi-Agent Support](#multi-agent-support) for the complete matrix and manual/UI-only boundaries. - **Built-in graph visualization** — 3D interactive UI at `localhost:9749`, served from the binary itself. - **Infrastructure-as-code indexing** — Dockerfiles, Kubernetes manifests, and Kustomize overlays indexed as graph nodes with cross-references. `Resource` nodes for K8s kinds, `Module` nodes for Kustomize overlays with `IMPORTS` edges to referenced resources. - **17 MCP tools** — search, trace, architecture, impact analysis, targeted index-coverage checks, Cypher queries, dead code detection, cross-service HTTP linking, ADR management, and more. @@ -477,7 +477,7 @@ Restart your agent. Verify with `/mcp` — you should see `codebase-memory-mcp` ## Multi-Agent Support -`install` configures 45 client surfaces: 39 detected automatically and 6 +`install` configures 46 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, @@ -544,6 +544,7 @@ overwrite user-modified agents. | Pi | Detected | — | `~/.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 @@ -892,7 +893,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 fdfde941e..88480526e 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,7 +4,7 @@ codebase-memory-mcp — Code Intelligence Knowledge Graph for AI Coding Agents - + @@ -68,7 +68,7 @@ "Infrastructure-as-code indexing for Dockerfiles, Kubernetes, and Kustomize", "Built-in 3D graph visualization UI", "Auto-sync background watcher for incremental re-indexing", - "One command configures 45 automatic/conditional client surfaces" + "One command configures 46 automatic/conditional client surfaces" ], "author": { "@type": "Organization", @@ -157,7 +157,7 @@ "name": "Which AI coding agents work with codebase-memory-mcp?", "acceptedAnswer": { "@type": "Answer", - "text": "A single install command configures 45 automatic/conditional client surfaces. The 39 detected surfaces are 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). Continue / cn, Visual Studio, TRAE, Roo Code, IBM Bob IDE, and Sourcegraph Cody are conditional or explicit integrations. Qodo, Warp MCP, JetBrains AI/ACP, GitHub Copilot coding agent, Jules, CodeRabbit, Replit, BLACKBOX AI, Plandex, and SWE-agent require manual, UI, cloud, or repository-managed setup and are not counted among the 45." + "text": "A single install command configures 46 automatic/conditional client surfaces. The 40 detected surfaces are 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. Continue / cn, Visual Studio, TRAE, Roo Code, IBM Bob IDE, and Sourcegraph Cody are conditional or explicit integrations. Qodo, Warp MCP, JetBrains AI/ACP, GitHub Copilot coding agent, Jules, CodeRabbit, Replit, BLACKBOX AI, Plandex, and SWE-agent require manual, UI, cloud, or repository-managed setup and are not counted among the 46." } }, { @@ -527,12 +527,12 @@

How do I install codebase-memory-mcp?

"Index this project"

- One command configures 45 automatic/conditional client surfaces. Detected automatically (39): + One command configures 46 automatic/conditional client surfaces. 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, and Oh My Pi (omp). + 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, diff --git a/docs/llms.txt b/docs/llms.txt index f3c23970b..b0f76c2e6 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -12,7 +12,7 @@ - Semantic & similarity edges: SEMANTICALLY_RELATED (vocabulary-mismatch matches) and SIMILAR_TO (MinHash + LSH near-clone / duplicate detection). - Cross-repo intelligence: CROSS_* edges link nodes across multiple repos indexed in one store; multi-galaxy 3D layout and cross-repo architecture summary. - Cross-service linking: HTTP route ↔ call-site matching, plus gRPC/GraphQL/tRPC detection and pub/sub channels (EMITS/LISTENS_ON for Socket.IO, EventEmitter, generic buses). -- 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. - Agent profiles: documented custom-agent formats receive Scout (fast/provisional), Verify (default/task-directed), and Auditor (bounded/full verification) definitions. Every direct tier checks exact path/scope coverage with check_index_coverage and falls back to source for flagged gaps; unsafe child-MCP formats use explicit parent handoff. Kiro and Junie use positive-allowlist Scout/Analysis server profiles (7/11 tools); Qoder combines named-server selection with exact tier-specific MCP tool IDs, while Factory uses exact registered IDs without additive whole-server exposure. Foreign Junie aliases are preserved and force parent handoff. - Performance: Linux kernel (28M LOC, 75K files) full index in 3 minutes → 4.81M nodes, 7.72M edges; Cypher queries in under 1ms. - Distribution: verified runtime set (native executable plus authenticated release-owned assets); fully static Linux builds are available in the `-portable` archives. Also available through npm, PyPI, Homebrew, Scoop, Winget, Chocolatey, AUR, and `go install`. diff --git a/pkg/npm/README.md b/pkg/npm/README.md index c12a2d9c6..d41821a31 100644 --- a/pkg/npm/README.md +++ b/pkg/npm/README.md @@ -7,7 +7,7 @@ **The fastest and most efficient code intelligence engine for AI coding agents.** Full-indexes an average repository in milliseconds, the Linux kernel (28M LOC, 75K files) in 3 minutes. Answers structural queries in under 1ms. This npm wrapper downloads, verifies, and caches the selected native runtime set: the executable, its authenticated integration asset, and—when requested—the content-addressed UI pack. -High-quality parsing through [tree-sitter](https://tree-sitter.github.io/tree-sitter/) AST analysis across 162 languages — producing a persistent knowledge graph of functions, classes, call chains, HTTP routes, and cross-service links. 17 MCP tools. No hosted service or API key; this package requires Node.js to manage and launch the cached native runtime set. Plug and play across 45 automatic/conditional client surfaces. +High-quality parsing through [tree-sitter](https://tree-sitter.github.io/tree-sitter/) AST analysis across 162 languages — producing a persistent knowledge graph of functions, classes, call chains, HTTP routes, and cross-service links. 17 MCP tools. No hosted service or API key; this package requires Node.js to manage and launch the cached native runtime set. Plug and play across 46 automatic/conditional client surfaces. ## Installation @@ -31,8 +31,8 @@ Restart your agent. Say **"Index this project"** — done. - **Plug and play** — verified native runtime sets for macOS (arm64/amd64), Linux (arm64/amd64), and Windows (arm64/amd64). No Docker or API keys; Node.js owns package download, cache repair, and launch. - **162 languages** — vendored tree-sitter grammars compiled into the binary. Nothing to install, nothing that breaks. - **120x fewer tokens** — 5 structural queries: ~3,400 tokens vs ~412,000 via file-by-file search. -- **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 4c07a583d..b3b6deb48 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,15 @@ 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: { + char override[1024]; + if (cbm_agent_env_config_override("AGENTTY_MCP_CONFIG", options->home_dir, override, + sizeof(override))) { + int written = snprintf(path_out, path_out_size, "%s", override); + 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 +675,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 +1480,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: @@ -1489,6 +1504,41 @@ int cbm_agent_client_install_mcp(cbm_agent_client_id_t id, const char *config_pa : CBM_AGENT_EDIT_NOT_APPLICABLE; } +bool cbm_agent_env_config_override(const char *env_name, const char *home_dir, char *resolved, + size_t resolved_size) { + if (!env_name || !resolved || resolved_size == 0U) { + return false; + } + char value[1024]; + const char *configured = getenv(env_name); + if (!configured || configured[0] == '\0' || strlen(configured) >= sizeof(value)) { + return false; + } + snprintf(value, sizeof(value), "%s", configured); + /* Expand a leading ~ against the home directory, matching the + * CLAUDE_CONFIG_DIR / GROK_HOME handling in cli.c. */ + if (value[0] == '~' && (value[1] == '\0' || value[1] == '/')) { + if (!home_dir || home_dir[0] == '\0') { + return false; + } + char expanded[1024]; + int written = value[1] == '\0' + ? snprintf(expanded, sizeof(expanded), "%s", home_dir) + : snprintf(expanded, sizeof(expanded), "%s%s", home_dir, value + 1); + if (written < 0 || (size_t)written >= sizeof(expanded) || + (size_t)written >= resolved_size) { + return false; + } + snprintf(resolved, resolved_size, "%s", expanded); + return true; + } + if (strlen(value) >= resolved_size) { + return false; + } + snprintf(resolved, resolved_size, "%s", value); + return true; +} + int cbm_agent_client_remove_mcp(cbm_agent_client_id_t id, const char *config_path, const char *binary_path) { if (!config_path || config_path[0] == '\0' || !binary_path || binary_path[0] == '\0' || diff --git a/src/cli/agent_clients.h b/src/cli/agent_clients.h index ccf9f81a1..ef1a62ede 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; @@ -115,6 +116,13 @@ int cbm_agent_client_install_mcp(cbm_agent_client_id_t id, const char *config_pa int cbm_agent_client_remove_mcp(cbm_agent_client_id_t id, const char *config_path, const char *binary_path); +/* Shared env-override resolution for clients whose documented config path can + * be redirected by an environment variable (CLAUDE_CONFIG_DIR, GROK_HOME, + * VIBE_HOME, AGENTTY_MCP_CONFIG). Expands a leading ~ against the home + * directory; returns false when the variable is unset or empty. */ +bool cbm_agent_env_config_override(const char *env_name, const char *home_dir, char *resolved, + size_t resolved_size); + #ifdef __cplusplus } #endif diff --git a/src/cli/cli.c b/src/cli/cli.c index 93df2dabe..f3698d463 100644 --- a/src/cli/cli.c +++ b/src/cli/cli.c @@ -9646,6 +9646,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); } } } @@ -10808,6 +10812,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 ──────────────────────────────────────── */ @@ -12135,6 +12143,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 e5e10417c..0f281a687 100644 --- a/src/cli/cli.h +++ b/src/cli/cli.h @@ -231,6 +231,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 0beb974a5..00ced7c9b 100644 --- a/src/main.c +++ b/src/main.c @@ -1124,7 +1124,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"); @@ -1133,7 +1133,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 df8515e73..c30b8af48 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 a77abc0a3..eae34bce7 100644 --- a/tests/test_cli.c +++ b/tests/test_cli.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -6743,6 +6744,80 @@ 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 *saved = save_test_env("AGENTTY_MCP_CONFIG"); + char override_path[512]; + snprintf(override_path, sizeof(override_path), "%s/override.json", tmpdir); + cbm_setenv("AGENTTY_MCP_CONFIG", override_path, 1); + + cbm_agent_client_resolve_options_t options; + memset(&options, 0, sizeof(options)); + options.home_dir = tmpdir; + + 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); + + /* Tilde expansion goes against the home directory. */ + cbm_setenv("AGENTTY_MCP_CONFIG", "~/agentty-override.json", 1); + 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-override.json", tmpdir); + ASSERT_STR_EQ(config_path, expected); + + /* Unset falls back to ~/.agentty/mcp.json. */ + cbm_unsetenv("AGENTTY_MCP_CONFIG"); + ASSERT_EQ(cbm_agent_client_resolve_path(CBM_AGENT_CLIENT_AGENTTY, &options, config_path, + sizeof(config_path)), + 0); + snprintf(expected, sizeof(expected), "%s/.agentty/mcp.json", tmpdir); + ASSERT_STR_EQ(config_path, expected); + + restore_test_env("AGENTTY_MCP_CONFIG", saved); + test_rmdir_r(tmpdir); + 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) { @@ -6860,14 +6935,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])) { @@ -6880,9 +6959,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])) { @@ -6895,9 +6974,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])) { @@ -6916,7 +6995,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"); } @@ -6925,10 +7004,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])) { @@ -6976,6 +7055,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++) { @@ -7026,6 +7106,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++) { @@ -7085,6 +7168,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++) { @@ -7193,6 +7277,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]); } @@ -7202,6 +7293,119 @@ 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}; + if (!test_file_contains_all(override_path, override_markers, 3) || + stat(override_path, &override_state) != 0) + FAIL("install must honor $AGENTTY_MCP_CONFIG and write the override file"); + 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); + if (!default_untouched) + FAIL("install must not re-add the entry to ~/.agentty/mcp.json while " + "$AGENTTY_MCP_CONFIG is set"); + argv[0] = "uninstall"; + 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); + if (!override_entry_removed) + FAIL("uninstall must honor $AGENTTY_MCP_CONFIG and clean the override file"); + + 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 (!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"); @@ -17332,6 +17536,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++) { @@ -17846,7 +18063,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); @@ -17859,6 +18076,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);