From a1fc46dd5bbcd3f294eb0449916db0ece2c25103 Mon Sep 17 00:00:00 2001 From: DevinZeng Date: Mon, 15 Jun 2026 16:14:53 +0800 Subject: [PATCH 1/3] feat: add CodeBuddy support --- CHANGELOG.md | 2 +- README.md | 6 +- docs/02-usage/020_running.md | 6 +- docs/02-usage/030_clients.md | 93 ++++++++++++++++++- docs/02-usage/040_workflow.md | 4 +- docs/02-usage/050_configuration.md | 3 +- src/serena/config/client_setup.py | 23 ++++- src/serena/hooks.py | 5 +- .../resources/config/contexts/codebuddy.yml | 52 +++++++++++ .../java/test_jdtls_path_resolution.py | 6 +- 10 files changed, 181 insertions(+), 19 deletions(-) create mode 100644 src/serena/resources/config/contexts/codebuddy.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index d1f200e495..0706831986 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Status of the `main` branch. Changes prior to the next official version change w - Fix `--project-from-cwd` hijacking git worktrees nested under a Serena project. `find_project_root` now walks up in a single pass so the nearest project boundary wins (either a `.serena/project.yml` or a `.git`, including worktree/submodule pointer files), instead of preferring an ancestor's - `.serena/project.yml` over a closer `.git`. This previously bound CLI agents (Claude Code, Codex, + `.serena/project.yml` over a closer `.git`. This previously bound CLI agents (Claude Code, CodeBuddy, Codex, Gemini) launched from inside a worktree to the parent repo, causing stale reads and misdirected edits. - Fix: CLI flags on `start-mcp-server` could incorrectly be saved to the global configuration file if the list of projects was modified (triggering a save of the configuration with transient overrides applied) diff --git a/README.md b/README.md index 44cdee0fc3..2d1c68a957 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ but an LLM is required to do the actual work, orchestrating tool use. Serena can extend the functionality of your existing AI client via the **model context protocol (MCP)**. Most modern AI chat clients directly support MCP, including -* terminal-based clients like Claude Code, Codex, OpenCode, or Gemini-CLI, +* terminal-based clients like Claude Code, CodeBuddy, Codex, OpenCode, or Gemini-CLI, * IDEs and IDE assistant plugins for VSCode, Cursor and JetBrains IDEs (Copilot, Junie, JetBrains AI Assistant, etc.), * desktop and web clients like Claude Desktop, Codex App, or OpenWebUI. @@ -184,7 +184,7 @@ via a persistent REPL-style interface. ### Basic Features Beyond its semantic capabilities, Serena includes a set of basic utilities for completeness. -When Serena is used inside an agentic harness such as Claude Code or Codex, these tools are typically disabled by default, +When Serena is used inside an agentic harness such as Claude Code, CodeBuddy, or Codex, these tools are typically disabled by default, since the surrounding harness already provides overlapping file, search, and shell capabilities. - **`search_for_pattern`** – flexible regex search across the codebase @@ -242,7 +242,7 @@ By default, this will set up Serena to use the language server backend. To use t Either way, you should receive a success message indicating that Serena has been initialised successfully. **Configuring Your Client**. To connect Serena to your preferred MCP client, you typically need to [configure a launch command in your client](https://oraios.github.io/serena/02-usage/030_clients.html). -Follow the link for specific instructions on how to set up Serena for Claude Code, Codex, Claude Desktop, MCP-enabled IDEs and other clients (such as local and web-based GUIs). +Follow the link for specific instructions on how to set up Serena for Claude Code, CodeBuddy, Codex, Claude Desktop, MCP-enabled IDEs and other clients (such as local and web-based GUIs). > [!TIP] > While getting started quickly is easy, Serena is a powerful toolkit with many configuration options. diff --git a/docs/02-usage/020_running.md b/docs/02-usage/020_running.md index 84de5490d3..06bfb68802 100644 --- a/docs/02-usage/020_running.md +++ b/docs/02-usage/020_running.md @@ -33,7 +33,7 @@ which has several advantages over the language server-based approach. ### Standard I/O Mode -The typical usage involves the client (e.g. Claude Code, Codex or Cursor) running +The typical usage involves the client (e.g. Claude Code, CodeBuddy, Codex or Cursor) running the MCP server as a subprocess and using the process' stdin/stdout streams to communicate with it. In order to launch the server, the client thus needs to be provided with the command to run the MCP server. @@ -49,7 +49,7 @@ case, you can simply run the `start-mcp-server` command without any additional o serena start-mcp-server -See the section ["Configuring Your MCP Client"](030_clients) for specific information on how to configure your MCP client (e.g. Claude Code, Codex, Cursor, etc.) +See the section ["Configuring Your MCP Client"](030_clients) for specific information on how to configure your MCP client (e.g. Claude Code, CodeBuddy, Codex, Cursor, etc.) to use such a launch command. (streamable-http)= @@ -95,7 +95,7 @@ Some useful options include: (walking up the parent directories and activating the nearest one that contains either `.serena/project.yml` or `.git`, if any). The nearest boundary wins, so a git worktree nested under another Serena project resolves to the worktree itself rather than the ancestor project. - This option is intended for CLI-based agents like Claude Code, Gemini and Codex, which are typically started from within the project directory + This option is intended for CLI-based agents like Claude Code, CodeBuddy, Gemini and Codex, which are typically started from within the project directory and which do not change directories during their operation. * `--language-backend JetBrains`: use the Serena JetBrains Plugin as the language backend (overriding the default backend configured in the central configuration) * `--context `: specify the operation [context](contexts) in which Serena shall operate diff --git a/docs/02-usage/030_clients.md b/docs/02-usage/030_clients.md index 0a6e0af54a..9d12fbb457 100644 --- a/docs/02-usage/030_clients.md +++ b/docs/02-usage/030_clients.md @@ -21,12 +21,12 @@ Depending on your needs, you might want to further customize Serena's behaviour * [adjusting configuration](050_configuration). **Mode of Operation**. -Note that some clients have a per-workspace MCP configuration (e.g, VSCode and Claude Code), +Note that some clients have a per-workspace MCP configuration (e.g, VSCode, Claude Code, and CodeBuddy), while others have a global MCP configuration (e.g. Codex and Claude Desktop). - In the per-workspace case, you typically want to start Serena with your workspace directory as the project directory and never switch to a different project. This is achieved by specifying the - `--project ` argument with a single-project [context](#contexts) (e.g. `ide` or `claude-code`). + `--project ` argument with a single-project [context](#contexts) (e.g. `ide`, `claude-code`, or `codebuddy`). - In the global configuration case, you must first activate the project you want to work on, which you can do by asking the LLM to do so (e.g., "Activate the current dir as project using serena"). In such settings, the `activate_project` tool is required. @@ -47,7 +47,7 @@ In this case, a workaround is to provide the full path to the `serena` executabl **Serena's tools not being used**. With some clients, you may experience that Serena's tools are not being used. This is mainly due to problems in the client itself (like a poorly implemented tool discovery). To counteract this, -Serena comes with a set of commands that can be used in _hooks_. See the sections on hooks for Claude Code and VSCode below. +Serena comes with a set of commands that can be used in _hooks_. See the sections on hooks for Claude Code, CodeBuddy, and VSCode below. **Environment Variables**. Some language servers may require additional environment variables to be set (e.g. F# on macOS with Homebrew), @@ -225,6 +225,93 @@ The hooks will: For more details on Claude Code's hook system, see the [Claude Code hooks documentation](https://code.claude.com/docs/en/hooks). +## CodeBuddy + +Serena provides native support for CodeBuddy, a CLI coding agent that shares a similar architecture with Claude Code. +To set up the Serena MCP server for CodeBuddy, simply run: + + serena setup codebuddy + +### Manual Setup + +**Global Configuration**. To add the Serena MCP server for all your projects, use the user-level configuration of CodeBuddy and the `--project-from-cwd` flag: + +```bash +codebuddy mcp add --scope user serena -- serena start-mcp-server --context codebuddy --project-from-cwd +``` + +**Project-Level Configuration**. To add the Serena MCP server for a single project only: + +```bash +codebuddy mcp add serena -- serena start-mcp-server --context codebuddy --project "$(pwd)" +``` + +Confirm that CodeBuddy is connected to Serena by running the `/mcp` command and reconnecting if necessary. + +### Hooks + +CodeBuddy supports the same hook system as Claude Code. To set up hooks, add the following to your CodeBuddy settings file (`.codebuddy/settings.json` in your project directory, or `~/.codebuddy/settings.json` globally): + +```json +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "", + "hooks": [ + { + "type": "command", + "command": "serena-hooks remind --client=codebuddy" + } + ] + } + ], + "Notification": [ + { + "matcher": "", + "hooks": [ + { + "type": "command", + "command": "serena-hooks auto-approve --client=codebuddy" + } + ] + } + ], + "SessionStart": [ + { + "matcher": "", + "hooks": [ + { + "type": "command", + "command": "serena-hooks activate --client=codebuddy" + } + ] + } + ], + "Stop": [ + { + "matcher": "", + "hooks": [ + { + "type": "command", + "command": "serena-hooks cleanup --client=codebuddy" + } + ] + } + ] + } +} +``` + +### Hook Descriptions + +- **`remind`**: Remind the agent to use Serena's tools instead of built-in `grep` and `read` tools. +- **`activate`**: Prompt the agent to activate the project at session start and read Serena's instructions. +- **`cleanup`**: Clean up hook session data when the session ends. +- **`auto-approve`**: Auto-approve Serena tool calls whenever CodeBuddy is in a permissive + permission mode (`acceptEdits` or `auto`), so blanket approvals cover Serena's destructive + tools (e.g. `replace_symbol_body`, `rename_symbol`) instead of prompting on every call. + ## VSCode You can add Serena to VSCode by running the MCP: Add Server command. diff --git a/docs/02-usage/040_workflow.md b/docs/02-usage/040_workflow.md index 8fa10eca19..fcffa2e5f8 100644 --- a/docs/02-usage/040_workflow.md +++ b/docs/02-usage/040_workflow.md @@ -124,12 +124,12 @@ You can either choose to do this * "Activate the project my_project" Note that this option requires the `activate_project` tool to be active, - which it isn't in single-project [contexts](contexts) like `ide` or `claude-code` *if* a project is provided at startup. + which it isn't in single-project [contexts](contexts) like `ide`, `claude-code`, or `codebuddy` *if* a project is provided at startup. (The tool is deactivated, because we assume that in these contexts, user will only work on the single, open project and have no need to switch it.) * when the MCP server starts, by passing the project path or name as a command-line argument - (e.g. when using a single-project mode like `ide` or `claude-code`): `--project ` + (e.g. when using a single-project mode like `ide`, `claude-code`, or `codebuddy`): `--project ` When working with the JetBrains plugin, be sure to have the same project folder open as a project in your IDE, i.e. the folder that is activated in Serena should correspond to the root folder of the project in your IDE. diff --git a/docs/02-usage/050_configuration.md b/docs/02-usage/050_configuration.md index 983924bb92..70d1c0657a 100644 --- a/docs/02-usage/050_configuration.md +++ b/docs/02-usage/050_configuration.md @@ -71,6 +71,7 @@ Serena comes with pre-defined contexts: * `desktop-app`: Tailored for use with desktop applications like Claude Desktop. This is the default. The full set of Serena's tools is provided, as the application is assumed to have no prior coding-specific capabilities. * `claude-code`: Optimized for use with Claude Code, it disables tools that would duplicate Claude Code's built-in capabilities. +* `codebuddy`: Optimized for use with CodeBuddy, it disables tools that would duplicate CodeBuddy's built-in capabilities. * `codex`: Optimized for use with OpenAI Codex. * `ide`: Generic context for IDE assistants/coding agents, e.g. VSCode, Cursor, or Cline, focusing on augmenting existing capabilities. Basic file operations and shell execution are assumed to be handled by the assistant's own capabilities. @@ -80,7 +81,7 @@ Choose the context that best matches the type of integration you are using. Find the concrete definitions of the above contexts [here](https://github.com/oraios/serena/tree/main/src/serena/resources/config/contexts). -Note that the contexts `ide` and `claude-code` are **single-project contexts** (defining `single_project: true`). +Note that the contexts `ide`, `claude-code`, and `codebuddy` are **single-project contexts** (defining `single_project: true`). For such contexts, if a project is provided at startup, the set of tools is limited to those required by the project's concrete configuration, and other tools are excluded completely, allowing the set of tools to be minimal. Tools explicitly disabled by the project will not be available at all. Since changing the active project diff --git a/src/serena/config/client_setup.py b/src/serena/config/client_setup.py index 9116d01f12..6a1870738a 100644 --- a/src/serena/config/client_setup.py +++ b/src/serena/config/client_setup.py @@ -88,4 +88,25 @@ def apply(self) -> bool: return self._run_shell_command(f"codex mcp add serena -- {self.get_mcp_server_command()}") -client_setup_handlers = [ClientSetupHandlerClaudeCode(), ClientSetupHandlerCodex()] +class ClientSetupHandlerCodeBuddy(ClientSetupHandler): + def __init__(self) -> None: + super().__init__("codebuddy") + + def is_applicable(self) -> bool: + result = execute_shell_command("codebuddy --version") + return result.return_code == 0 and "CodeBuddy" in result.stdout + + def get_mcp_server_options(self) -> list[str]: + return ["--context=codebuddy", "--project-from-cwd"] + + def apply(self) -> bool: + cmd = f"codebuddy mcp add --scope user serena -- {self.get_mcp_server_command()}" + is_success = self._run_shell_command(cmd) + if is_success: + click.echo("\nIMPORTANT: We additionally recommend to set up hooks for CodeBuddy to ensure the best experience.") + click.echo(" Please read the instructions here:") + click.echo(" https://oraios.github.io/serena/02-usage/030_clients.html#codebuddy") + return is_success + + +client_setup_handlers = [ClientSetupHandlerClaudeCode(), ClientSetupHandlerCodeBuddy(), ClientSetupHandlerCodex()] diff --git a/src/serena/hooks.py b/src/serena/hooks.py index ef5e210675..989c7a9af0 100644 --- a/src/serena/hooks.py +++ b/src/serena/hooks.py @@ -22,6 +22,7 @@ class HookClient(Enum): """The client application that triggered the hook.""" CLAUDE_CODE = "claude-code" + CODEBUDDY = "codebuddy" VSCODE = "vscode" CODEX = "codex" @@ -361,7 +362,7 @@ def __init__(self, client: HookClient): self._file_path = str(file_path).strip() or None def is_grep_call(self) -> bool: - if self._client == HookClient.CLAUDE_CODE: + if self._client in (HookClient.CLAUDE_CODE, HookClient.CODEBUDDY): return self._tool_name == "grep" or "search_for_pattern" in self._tool_name if self._client == HookClient.CODEX and self._is_shell_command_call(): return self._command_name in self._GREP_SHELL_COMMANDS @@ -369,7 +370,7 @@ def is_grep_call(self) -> bool: return "grep" in self._tool_name def is_read_call(self) -> bool: - if self._client == HookClient.CLAUDE_CODE: + if self._client in (HookClient.CLAUDE_CODE, HookClient.CODEBUDDY): return self._tool_name == "read" or "read_file" in self._tool_name if self._client == HookClient.CODEX and self._is_shell_command_call(): return self._command_name in self._READ_SHELL_COMMANDS diff --git a/src/serena/resources/config/contexts/codebuddy.yml b/src/serena/resources/config/contexts/codebuddy.yml new file mode 100644 index 0000000000..9e2ff53e77 --- /dev/null +++ b/src/serena/resources/config/contexts/codebuddy.yml @@ -0,0 +1,52 @@ +description: CodeBuddy (CLI agent where file operations, basic edits, etc. are already covered; single project mode) +prompt: | + You are running in a CLI coding agent context where file operations, basic (line-based) edits and reads + as well as shell commands are handled by your own, internal tools. + + You have access to Serena's code intelligence tools that exploit the symbolic + structure of the code and are much more efficient than your own tools for most coding scenarios. + If you are working on any coding task and if Serena's tools are deferred, you + should load them all immediately, before performing any read, grep or bash commands. + If Serena's tools can be used to achieve your task, you should prioritize them. + **CRITICAL**: If you haven't seen Serena's tools yet, you should use the tool search tool to load all of them + **right now**. + For exploring and reading code in a token-efficient manner, use Serena's code-centric overview and symbolic search tools. + For edits, prefer Serena's editing tools since they are more token efficient. + Especially when editing code you have read through Serena, **ALWAYS** use Serena's tools for editing, because your built-in tools will deny such edits (they will assume you haven't read the content). + **CRITICAL**: Never use a tool before having read its schema via the tool-search tool! + + **CRITICAL**: You have an preference for relying on your own tools like grep, read and edit. It is of paramount + importance that you overcome this preference and instead develop a preference for using Serena's tools since they + are superior for most coding tasks. Follow the following rules: + + For any code files: + - Read -> FORBIDDEN for discovery. Use get_symbols_overview, then find_symbol with include_body. + You may use read only if you already have an overview of the file and if reading whole symbols + is inappropriate (e.g. a few lines of code). + - Glob (by name) -> Allowed for discovery only. + - Grep (content) -> Allowed for discovery only; follow up reads or reference searches must be Serena. + - Edit -> FORBIDDEN. Use replace_symbol_body / insert_*_symbol / replace_content. + + Disallowed reasoning. Do NOT use any of the following to justify Read/Edit on a code file: + - "I already know the path" + - "one Read call is faster than three Serena calls" + - "the built-in tool description says to use Read for known paths" + If you catch yourself reaching for one of these, that is the signal to switch to Serena. + +excluded_tools: + - create_text_file + - read_file + - execute_shell_command + - find_file + - list_dir + - search_for_pattern + +tool_description_overrides: {} + +# whether to assume that Serena shall only work on a single project in this context (provided that a project is given +# when Serena is started). +# If set to true and a project is provided at startup, the set of tools is limited to those required by the project's +# concrete configuration, and other tools are excluded completely, allowing the set of tools to be minimal. +# Tools explicitly disabled by the project will not be available at all. +# The `activate_project` tool is always disabled in this case, as project switching cannot be allowed. +single_project: true diff --git a/test/solidlsp/java/test_jdtls_path_resolution.py b/test/solidlsp/java/test_jdtls_path_resolution.py index 6ce204d1f5..9c9acd64a8 100644 --- a/test/solidlsp/java/test_jdtls_path_resolution.py +++ b/test/solidlsp/java/test_jdtls_path_resolution.py @@ -124,7 +124,7 @@ def test_raises_when_config_dir_missing(self, tmp_path: Path) -> None: (tmp_path / "plugins").mkdir() with patch("solidlsp.language_servers.eclipse_jdtls.PlatformUtils.get_platform_id") as mock_get_pid: mock_get_pid.return_value.value = "linux-x64" - with pytest.raises(SolidLSPException, match="Config directory .* not found"): + with pytest.raises(SolidLSPException, match=r"Config directory .* not found"): EclipseJDTLS.DependencyProvider._resolve_config_dir(tmp_path) def test_raises_for_unsupported_platform(self, tmp_path: Path) -> None: @@ -177,7 +177,7 @@ def test_parses_openjdk_17_output(self) -> None: def test_raises_when_java_home_property_missing(self) -> None: stderr = 'java version "21.0.0"\n' with patch("subprocess.run", return_value=self._fake_subprocess_result(stderr)): - with pytest.raises(SolidLSPException, match="Could not parse java.home"): + with pytest.raises(SolidLSPException, match=r"Could not parse java.home"): EclipseJDTLS.DependencyProvider._inspect_java("/usr/bin/fakejava") def test_raises_when_version_string_missing(self) -> None: @@ -350,7 +350,7 @@ def test_raises_when_plugins_dir_missing( def test_raises_when_lombok_jar_missing( self, jdtls_root: Path, tmp_path: Path, custom_settings: SolidLSPSettings.CustomLSSettings ) -> None: - with pytest.raises(SolidLSPException, match="lombok_path .* does not exist"): + with pytest.raises(SolidLSPException, match=r"lombok_path .* does not exist"): EclipseJDTLS.DependencyProvider._setup_from_existing_install( str(jdtls_root), str(tmp_path / "no-such-lombok.jar"), custom_settings ) From ba7e8835177f5db03e21d8e404e8e9d62d36e306 Mon Sep 17 00:00:00 2001 From: DevinZeng Date: Mon, 15 Jun 2026 18:25:54 +0800 Subject: [PATCH 2/3] fix: relax codebuddy version check in ClientSetupHandlerCodeBuddy.is_applicable() `codebuddy --version` outputs only the version number (e.g. "2.106.4") without the string "CodeBuddy", causing the `is_applicable()` check to return False and setup to fail with "not found or not functional". Checking only the exit code is sufficient to detect the binary. --- src/serena/config/client_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serena/config/client_setup.py b/src/serena/config/client_setup.py index 6a1870738a..6cac500e20 100644 --- a/src/serena/config/client_setup.py +++ b/src/serena/config/client_setup.py @@ -94,7 +94,7 @@ def __init__(self) -> None: def is_applicable(self) -> bool: result = execute_shell_command("codebuddy --version") - return result.return_code == 0 and "CodeBuddy" in result.stdout + return result.return_code == 0 def get_mcp_server_options(self) -> list[str]: return ["--context=codebuddy", "--project-from-cwd"] From 4e3d34954d335f6a5d828a36636e0a993d314df1 Mon Sep 17 00:00:00 2001 From: DevinZeng Date: Mon, 15 Jun 2026 18:43:04 +0800 Subject: [PATCH 3/3] fix: suppress stderr noise in ClientSetupHandler.is_applicable() checks `is_applicable()` calls `execute_shell_command()` without `capture_stderr=True`, so when a client binary is not installed (e.g. codex), the shell's 'command not found' error leaks directly to the terminal. Pass capture_stderr=True in all three handlers. --- src/serena/config/client_setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/serena/config/client_setup.py b/src/serena/config/client_setup.py index 6cac500e20..933ff4a311 100644 --- a/src/serena/config/client_setup.py +++ b/src/serena/config/client_setup.py @@ -53,7 +53,7 @@ def __init__(self) -> None: super().__init__("claude-code") def is_applicable(self) -> bool: - result = execute_shell_command("claude --version") + result = execute_shell_command("claude --version", capture_stderr=True) return result.return_code == 0 and "Claude" in result.stdout def get_mcp_server_options(self) -> list[str]: @@ -78,7 +78,7 @@ def __init__(self) -> None: super().__init__("codex") def is_applicable(self) -> bool: - result = execute_shell_command("codex --version") + result = execute_shell_command("codex --version", capture_stderr=True) return result.return_code == 0 and "codex-cli" in result.stdout def get_mcp_server_options(self) -> list[str]: @@ -93,7 +93,7 @@ def __init__(self) -> None: super().__init__("codebuddy") def is_applicable(self) -> bool: - result = execute_shell_command("codebuddy --version") + result = execute_shell_command("codebuddy --version", capture_stderr=True) return result.return_code == 0 def get_mcp_server_options(self) -> list[str]: