From 3912a07fcbfa24794c684d144eacd9877b6ec5fb Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Wed, 25 Mar 2026 17:01:23 +0100 Subject: [PATCH 01/25] Conftest: added get_pytest_markers --- test/conftest.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/conftest.py b/test/conftest.py index f8981251..d02c22be 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -8,6 +8,7 @@ from pathlib import Path from typing import Any import pytest +from _pytest.mark import Mark, MarkDecorator from sensai.util.logging import configure from serena.config.serena_config import SerenaConfig, SerenaPaths @@ -243,6 +244,39 @@ Flag indicating whether the tests are running in the GitHub CI environment. is_windows = platform.system() == "Windows" +_LANGUAGE_PYTEST_MARKERS: dict[Language, list[MarkDecorator | Mark]] = { + Language.CLOJURE: [ + pytest.mark.clojure, + pytest.mark.skipif(not is_clojure_cli_available(), reason="clojure CLI is not installed"), + ], + Language.CPP: [pytest.mark.cpp], + Language.CPP_CCLS: [pytest.mark.cpp], + Language.CSHARP: [pytest.mark.csharp], + Language.FSHARP: [pytest.mark.fsharp], + Language.GO: [pytest.mark.go], + Language.JAVA: [pytest.mark.java], + Language.KOTLIN: [pytest.mark.kotlin, pytest.mark.skipif(is_ci, reason="Kotlin LSP JVM crashes on restart in CI")], + Language.LEAN4: [pytest.mark.lean4, pytest.mark.skipif(_sh.which("lean") is None, reason="Lean is not installed")], + Language.PHP: [pytest.mark.php], + Language.PHP_PHPACTOR: [pytest.mark.php], + Language.POWERSHELL: [pytest.mark.powershell], + Language.PYTHON: [pytest.mark.python], + Language.PYTHON_JEDI: [pytest.mark.python], + Language.PYTHON_TY: [pytest.mark.python], + Language.RUST: [pytest.mark.rust], + Language.TYPESCRIPT: [pytest.mark.typescript], +} + + +def get_pytest_markers(language: Language) -> list[MarkDecorator | Mark]: + """Pytest markers for a language. + + The returned list contains the primary language marker and any + environment-dependent skip markers shared across the test suite. + """ + return _LANGUAGE_PYTEST_MARKERS[language] + + def _determine_disabled_languages() -> list[Language]: """ Determine which language tests should be disabled (based on the environment) From 20e14fa158be6ef4d1cd180beecf8e52f291590c Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Thu, 5 Mar 2026 16:35:27 +0100 Subject: [PATCH 02/25] Feature: move symbol WIP --- .../jetbrains/jetbrains_plugin_client.py | 15 +++++++++ src/serena/tools/jetbrains_tools.py | 32 ++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/serena/jetbrains/jetbrains_plugin_client.py b/src/serena/jetbrains/jetbrains_plugin_client.py index f237a079..56b08758 100644 --- a/src/serena/jetbrains/jetbrains_plugin_client.py +++ b/src/serena/jetbrains/jetbrains_plugin_client.py @@ -425,6 +425,21 @@ class JetBrainsPluginClient(ToStringMixin): self._postprocess_symbol_collection_response(symbol_collection) return symbol_collection + def move_symbol( + self, + name_path: str | None, + relative_path: str | None, + target_parent_name_path: str | None, + target_relative_path: str | None, + ) -> dict[str, Any]: + request_data = { + "namePath": name_path, + "relativePath": relative_path, + "targetParentNamePath": target_parent_name_path, + "targetRelativePath": target_relative_path, + } + return self._make_request("POST", "/moveSymbol", request_data) + def find_references(self, name_path: str, relative_path: str, include_quick_info: bool) -> jb.SymbolCollectionResponse: """ Finds references to a symbol. diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index bc0b99bc..68323872 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -6,7 +6,7 @@ import serena.jetbrains.jetbrains_types as jb from serena.jetbrains.jetbrains_plugin_client import JetBrainsPluginClient from serena.jetbrains.jetbrains_types import SymbolDTO from serena.symbol import JetBrainsSymbolDictGrouper -from serena.tools import Tool, ToolMarkerOptional, ToolMarkerSymbolicRead +from serena.tools import Tool, ToolMarkerOptional, ToolMarkerSymbolicEdit, ToolMarkerSymbolicRead log = logging.getLogger(__name__) @@ -111,6 +111,36 @@ class JetBrainsFindSymbolTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional): return self._limit_length(result, max_answer_chars, shortened_result_factories=[create_shortened_result]) +class JetBrainsMoveSymbolTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): + def apply( + self, + name_path: str | None = None, + relative_path: str | None = None, + target_parent_name_path: str | None = None, + target_relative_path: str | None = None, + ) -> str: + """ + Moves a symbol to a different location (file or parent symbol) using the JetBrains refactoring engine. + References to the symbol are automatically updated. + + :param name_path: the name path of the symbol to move (e.g. "MyClass/my_method"). + :param relative_path: the relative path to the file containing the symbol to move. + :param target_parent_name_path: the name path of the target parent symbol (e.g. "TargetClass"). + If None, the symbol is moved to the top level of the target file. + :param target_relative_path: the relative path to the target file. If None, the symbol + is moved within the same file. + :return: JSON string with the result of the operation. + """ + with JetBrainsPluginClient.from_project(self.project) as client: + response_dict = client.move_symbol( + name_path=name_path, + relative_path=relative_path, + target_parent_name_path=target_parent_name_path, + target_relative_path=target_relative_path, + ) + return self._to_json(response_dict) + + class JetBrainsFindReferencingSymbolsTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional): """ Finds symbols that reference the given symbol using the JetBrains backend From 2aa6603dbba0fbfeb6e1bf236cbea8971bd3d443 Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Sun, 22 Mar 2026 12:04:34 +0100 Subject: [PATCH 03/25] Added safe_delete and inline_symbol tools (JB backend) --- scripts/demo_run_tools.py | 15 ++++- .../jetbrains/jetbrains_plugin_client.py | 36 +++++++++++ .../config/internal_modes/jetbrains.yml | 3 + src/serena/tools/jetbrains_tools.py | 59 +++++++++++++++++++ 4 files changed, 111 insertions(+), 2 deletions(-) diff --git a/scripts/demo_run_tools.py b/scripts/demo_run_tools.py index bad7ed60..5d2b3358 100644 --- a/scripts/demo_run_tools.py +++ b/scripts/demo_run_tools.py @@ -4,6 +4,7 @@ for testing or development. Here the tools will be operation the serena repo its """ import json +from pathlib import Path from pprint import pprint from serena.agent import SerenaAgent @@ -14,13 +15,17 @@ from serena.tools import ( FindReferencingSymbolsTool, JetBrainsFindSymbolTool, JetBrainsGetSymbolsOverviewTool, + JetBrainsInlineSymbol, + JetBrainsSafeDeleteTool, SearchForPatternTool, ) if __name__ == "__main__": serena_config = SerenaConfig.from_config_file() serena_config.web_dashboard = False - agent = SerenaAgent(project=REPO_ROOT, serena_config=serena_config) + # project = Path(REPO_ROOT).parent / "serena-jetbrains-plugin-copy" + project = Path(REPO_ROOT) / "test/resources/repos/python/test_repo" + agent = SerenaAgent(project=str(project), serena_config=serena_config) # apply a tool find_symbol_tool = agent.get_tool(JetBrainsFindSymbolTool) @@ -28,9 +33,15 @@ if __name__ == "__main__": find_file_tool = agent.get_tool(FindFileTool) search_pattern_tool = agent.get_tool(SearchForPatternTool) overview_tool = agent.get_tool(JetBrainsGetSymbolsOverviewTool) + safe_delete_tool = agent.get_tool(JetBrainsSafeDeleteTool) + inline_symbol = agent.get_tool(JetBrainsInlineSymbol) result = agent.execute_task( - lambda: find_symbol_tool.apply("SerenaAgent/get_tool_description_override"), + lambda: inline_symbol.apply( + relative_path="test_repo/nested.py", + name_path="OuterClass/NestedClass", + keep_definition=True, + ) ) pprint(json.loads(result)) # input("Press Enter to continue...") diff --git a/src/serena/jetbrains/jetbrains_plugin_client.py b/src/serena/jetbrains/jetbrains_plugin_client.py index 56b08758..b79b226a 100644 --- a/src/serena/jetbrains/jetbrains_plugin_client.py +++ b/src/serena/jetbrains/jetbrains_plugin_client.py @@ -519,6 +519,42 @@ class JetBrainsPluginClient(ToStringMixin): } return cast(jb.TypeHierarchyResponse, self._make_request("POST", "/getSubtypes", request_data)) + def safe_delete(self, name_path: str | None, relative_path: str, delete_even_if_used: bool, propagate: bool) -> dict[str, Any]: + """ + Safely deletes a symbol, checking for usages first. + + :param name_path: the name path of the symbol to delete + :param relative_path: the relative path to the file containing the symbol + :param delete_even_if_used: if True, delete the symbol even if it has usages + """ + request_data = { + "namePath": name_path, + "relativePath": relative_path, + "deleteEvenIfUsed": delete_even_if_used, + "propagate": propagate, + } + return self._make_request("POST", "/safeDelete", request_data) + + def inline_symbol( + self, + name_path: str, + relative_path: str, + keep_definition: bool, + ) -> dict[str, Any]: + """ + Inlines a method, replacing all call sites with the method body. + + :param name_path: the name path of the method to inline + :param relative_path: the relative path to the file containing the method + :param keep_definition: if True, keep the original method definition after inlining + """ + request_data = { + "namePath": name_path, + "relativePath": relative_path, + "keepDefinition": keep_definition, + } + return self._make_request("POST", "/inlineSymbol", request_data) + def rename_symbol( self, name_path: str, relative_path: str, new_name: str, rename_in_comments: bool, rename_in_text_occurrences: bool ) -> None: diff --git a/src/serena/resources/config/internal_modes/jetbrains.yml b/src/serena/resources/config/internal_modes/jetbrains.yml index 863f3beb..090c6b8f 100644 --- a/src/serena/resources/config/internal_modes/jetbrains.yml +++ b/src/serena/resources/config/internal_modes/jetbrains.yml @@ -13,4 +13,7 @@ included_optional_tools: - jet_brains_find_symbol - jet_brains_find_referencing_symbols - jet_brains_get_symbols_overview + - jet_brains_move_symbol - jet_brains_type_hierarchy + - jet_brains_safe_delete + - jet_brains_inline_symbol diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index 68323872..f96bcd67 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -141,6 +141,65 @@ class JetBrainsMoveSymbolTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): return self._to_json(response_dict) +class JetBrainsSafeDeleteTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): + def apply( + self, + relative_path: str, + name_path: str | None = None, + delete_even_if_used: bool = False, + propagate: bool = False, + ) -> str: + """ + Safely deletes a symbol, checking for usages first. It is also + possible to request deleting of usages and cleaning up of unused code. + + :param relative_path: the relative path to the file containing the symbol to delete. + :param name_path: the name path of the symbol to delete. + A name path identifies a symbol within a source file, e.g. "MyClass/my_method". + :param delete_even_if_used: whether to force deletion even if the symbol still has usages. + Default is False (safe mode: will report usages instead of deleting). + :param propagate: whether to propagate the deletion to usages of the symbol and also + remove symbols that become unused after the deletion. Default is False. + :return: JSON string with the result of the operation. + """ + with JetBrainsPluginClient.from_project(self.project) as client: + response_dict = client.safe_delete( + name_path=name_path, + relative_path=relative_path, + delete_even_if_used=delete_even_if_used, + propagate=propagate, + ) + return self._to_json(response_dict) + + +class JetBrainsInlineSymbol(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): + def apply( + self, + name_path: str, + relative_path: str, + keep_definition: bool = False, + ) -> str: + """ + Inlines a symbol (usually method, but also classes may be amenable to inlining, + which turns invocation into anonymous class creation), + replacing all call sites with the method's body. + An error is raised when requested on a symbol that cannot be inlined. + + :param name_path: the name path of the method to inline. + :param relative_path: the relative path to the file containing the method to inline. + :param keep_definition: whether to keep the original method definition after inlining all call sites. + May be ignored in some cases (e.g., when inlining a class).. + :return: JSON string with the result of the operation. + """ + with JetBrainsPluginClient.from_project(self.project) as client: + response_dict = client.inline_symbol( + name_path=name_path, + relative_path=relative_path, + keep_definition=keep_definition, + ) + return self._to_json(response_dict) + + class JetBrainsFindReferencingSymbolsTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional): """ Finds symbols that reference the given symbol using the JetBrains backend From 9735733802fba8d4c77bcc77acf11065bf4f5e2f Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Tue, 24 Mar 2026 11:53:51 +0100 Subject: [PATCH 04/25] Added SafeDeleteSymbol in LS world --- .../config/internal_modes/jetbrains.yml | 1 + src/serena/tools/symbol_tools.py | 41 ++++++++ src/serena/tools/tools_base.py | 13 ++- .../repos/typescript/test_repo/index.ts | 4 + .../__snapshots__/test_symbol_editing.ambr | 24 +++++ test/serena/test_serena_agent.py | 98 ++++++++++++++++++- 6 files changed, 177 insertions(+), 4 deletions(-) diff --git a/src/serena/resources/config/internal_modes/jetbrains.yml b/src/serena/resources/config/internal_modes/jetbrains.yml index 090c6b8f..f9e60a4b 100644 --- a/src/serena/resources/config/internal_modes/jetbrains.yml +++ b/src/serena/resources/config/internal_modes/jetbrains.yml @@ -9,6 +9,7 @@ excluded_tools: - find_referencing_symbols - get_symbols_overview - restart_language_server + - safe_delete_symbol included_optional_tools: - jet_brains_find_symbol - jet_brains_find_referencing_symbols diff --git a/src/serena/tools/symbol_tools.py b/src/serena/tools/symbol_tools.py index 2df83bc1..2cf5c178 100644 --- a/src/serena/tools/symbol_tools.py +++ b/src/serena/tools/symbol_tools.py @@ -430,3 +430,44 @@ class RenameSymbolTool(Tool, ToolMarkerSymbolicEdit): code_editor = self.create_code_editor() status_message = code_editor.rename_symbol(name_path, relative_file_path=relative_path, new_name=new_name) return status_message + + +class SafeDeleteSymbol(Tool, ToolMarkerSymbolicEdit): + def apply( + self, + name_path_pattern: str, + relative_path: str, + ) -> str: + """ + Deletes the symbol if it is safe to do so (i.e., if there are no references to it) + or returns a list of references to it. + + :param name_path_pattern: name path of the symbol to delete (definitions in the `find_symbol` tool apply) + :param relative_path: the relative path to the file containing the symbol to delete + """ + ls_symbol_retriever = self.create_language_server_symbol_retriever() + symbol = ls_symbol_retriever.find_unique(name_path_pattern, substring_matching=False, within_relative_path=relative_path) + symbol_rel_path = symbol.relative_path + assert symbol_rel_path is not None, f"Symbol {name_path_pattern} has no relative path, this is likely a bug." + assert symbol_rel_path == relative_path, f"Symbol {name_path_pattern} is not in the expected relative path {relative_path}." + symbol_name_path = symbol.get_name_path() + + symbol_line = symbol.line + symbol_col = symbol.column + assert symbol_line is not None and symbol_col is not None, ( + f"Symbol {name_path_pattern} has no identifier position, this is likely a bug." + ) + lang_server = ls_symbol_retriever.get_language_server(symbol_rel_path) + references_locations = lang_server.request_references(symbol_rel_path, symbol_line, symbol_col) + file_to_lines: dict[str, list[int]] = defaultdict(list) + if references_locations: + for ref_loc in references_locations: + ref_relative_path = ref_loc.get("relativePath") + if ref_relative_path is None: + continue + file_to_lines[ref_relative_path].append(ref_loc["range"]["start"]["line"]) + if file_to_lines: + return f"Cannot delete, the symbol {symbol_name_path} is referenced in: {self._to_json(file_to_lines)}" + code_editor = self.create_ls_code_editor() + code_editor.delete_symbol(symbol_name_path, relative_file_path=symbol_rel_path) + return SUCCESS_RESULT diff --git a/src/serena/tools/tools_base.py b/src/serena/tools/tools_base.py index 18eed68f..200c2ae3 100644 --- a/src/serena/tools/tools_base.py +++ b/src/serena/tools/tools_base.py @@ -21,7 +21,7 @@ from solidlsp.ls_exceptions import SolidLSPException if TYPE_CHECKING: from serena.agent import SerenaAgent - from serena.code_editor import CodeEditor + from serena.code_editor import CodeEditor, LanguageServerCodeEditor from serena.symbol import LanguageServerSymbolRetriever log = logging.getLogger(__name__) @@ -58,16 +58,23 @@ class Component(ABC): return self.agent.get_active_project_or_raise() def create_code_editor(self) -> "CodeEditor": - from ..code_editor import JetBrainsCodeEditor, LanguageServerCodeEditor + from ..code_editor import JetBrainsCodeEditor match self.agent.get_language_backend(): case LanguageBackend.LSP: - return LanguageServerCodeEditor(self.create_language_server_symbol_retriever()) + return self.create_ls_code_editor() case LanguageBackend.JETBRAINS: return JetBrainsCodeEditor(project=self.project) case _: raise ValueError + def create_ls_code_editor(self) -> "LanguageServerCodeEditor": + from ..code_editor import LanguageServerCodeEditor + + if not self.agent.is_using_language_server(): + raise Exception("Cannot create LanguageServerCodeEditor; agent is not in language server mode.") + return LanguageServerCodeEditor(self.create_language_server_symbol_retriever()) + class ToolMarker: """ diff --git a/test/resources/repos/typescript/test_repo/index.ts b/test/resources/repos/typescript/test_repo/index.ts index 7b8bd1da..724a67c6 100644 --- a/test/resources/repos/typescript/test_repo/index.ts +++ b/test/resources/repos/typescript/test_repo/index.ts @@ -14,3 +14,7 @@ export function helperFunction() { } helperFunction(); + +export function unusedStandaloneFunction(): string { + return "This function is not referenced anywhere"; +} diff --git a/test/serena/__snapshots__/test_symbol_editing.ambr b/test/serena/__snapshots__/test_symbol_editing.ambr index 347862ca..f614627e 100644 --- a/test/serena/__snapshots__/test_symbol_editing.ambr +++ b/test/serena/__snapshots__/test_symbol_editing.ambr @@ -76,6 +76,10 @@ helperFunction(); + export function unusedStandaloneFunction(): string { + return "This function is not referenced anywhere"; + } + ''' # --- # name: test_delete_symbol_vue[test_case0] @@ -736,6 +740,10 @@ helperFunction(); + export function unusedStandaloneFunction(): string { + return "This function is not referenced anywhere"; + } + ''' # --- # name: test_insert_in_rel_to_symbol[test_case2-before] @@ -761,6 +769,10 @@ helperFunction(); + export function unusedStandaloneFunction(): string { + return "This function is not referenced anywhere"; + } + ''' # --- # name: test_insert_in_rel_to_symbol[test_case3-after] @@ -786,6 +798,10 @@ helperFunction(); + export function unusedStandaloneFunction(): string { + return "This function is not referenced anywhere"; + } + ''' # --- # name: test_insert_in_rel_to_symbol[test_case3-before] @@ -811,6 +827,10 @@ helperFunction(); + export function unusedStandaloneFunction(): string { + return "This function is not referenced anywhere"; + } + ''' # --- # name: test_insert_in_rel_to_symbol_vue[test_case0-after] @@ -1888,6 +1908,10 @@ helperFunction(); + export function unusedStandaloneFunction(): string { + return "This function is not referenced anywhere"; + } + ''' # --- # name: test_replace_body_vue[test_case0] diff --git a/test/serena/test_serena_agent.py b/test/serena/test_serena_agent.py index 0b40ab28..e9dc6b2e 100644 --- a/test/serena/test_serena_agent.py +++ b/test/serena/test_serena_agent.py @@ -12,7 +12,14 @@ import pytest from serena.agent import SerenaAgent from serena.config.serena_config import ProjectConfig, RegisteredProject, SerenaConfig from serena.project import Project -from serena.tools import SUCCESS_RESULT, FindReferencingSymbolsTool, FindSymbolTool, ReplaceContentTool, ReplaceSymbolBodyTool +from serena.tools import ( + SUCCESS_RESULT, + FindReferencingSymbolsTool, + FindSymbolTool, + ReplaceContentTool, + ReplaceSymbolBodyTool, + SafeDeleteSymbol, +) from solidlsp.ls_config import Language from solidlsp.ls_types import SymbolKind from test.conftest import get_repo_path, is_ci, language_tests_enabled @@ -589,3 +596,92 @@ class TestSerenaAgent: relative_path="ws_manager.js", mode="regex", ) + + @pytest.mark.parametrize( + "serena_agent,name_path,relative_path", + [ + pytest.param( + Language.PYTHON, + "User", + os.path.join("test_repo", "models.py"), + marks=pytest.mark.python, + ), + pytest.param( + Language.JAVA, + "Model", + os.path.join("src", "main", "java", "test_repo", "Model.java"), + marks=pytest.mark.java, + ), + pytest.param( + Language.KOTLIN, + "Model", + os.path.join("src", "main", "kotlin", "test_repo", "Model.kt"), + marks=[pytest.mark.kotlin] + ([pytest.mark.skip(reason="Kotlin LSP JVM crashes on restart in CI")] if is_ci else []), + ), + pytest.param( + Language.TYPESCRIPT, + "helperFunction", + "index.ts", + marks=pytest.mark.typescript, + ), + ], + indirect=["serena_agent"], + ) + def test_safe_delete_symbol_blocked_by_references(self, serena_agent: SerenaAgent, name_path: str, relative_path: str): + """ + Tests that SafeDeleteSymbol refuses to delete a symbol that is referenced elsewhere + and returns a message listing the referencing files. + """ + # wrap in modification context as a safety net: if the tool has a bug and deletes anyway, + # the file will be restored, preventing corruption of test resources + with project_file_modification_context(serena_agent, relative_path): + safe_delete_tool = serena_agent.get_tool(SafeDeleteSymbol) + result = safe_delete_tool.apply(name_path_pattern=name_path, relative_path=relative_path) + assert "Cannot delete" in result, f"Expected deletion to be blocked due to existing references, but got: {result}" + assert "referenced in" in result, f"Expected reference information in result, but got: {result}" + + @pytest.mark.parametrize( + "serena_agent,name_path,relative_path", + [ + pytest.param( + Language.PYTHON, + "Timer", + os.path.join("test_repo", "utils.py"), + marks=pytest.mark.python, + ), + pytest.param( + Language.JAVA, + "ModelUser", + os.path.join("src", "main", "java", "test_repo", "ModelUser.java"), + marks=pytest.mark.java, + ), + pytest.param( + Language.KOTLIN, + "ModelUser", + os.path.join("src", "main", "kotlin", "test_repo", "ModelUser.kt"), + marks=[pytest.mark.kotlin] + ([pytest.mark.skip(reason="Kotlin LSP JVM crashes on restart in CI")] if is_ci else []), + ), + pytest.param( + Language.TYPESCRIPT, + "unusedStandaloneFunction", + "index.ts", + marks=pytest.mark.typescript, + ), + ], + indirect=["serena_agent"], + ) + def test_safe_delete_symbol_succeeds_when_no_references(self, serena_agent: SerenaAgent, name_path: str, relative_path: str): + """ + Tests that SafeDeleteSymbol successfully deletes a symbol that has no references + and that the symbol is actually removed from the file. + """ + with project_file_modification_context(serena_agent, relative_path): + safe_delete_tool = serena_agent.get_tool(SafeDeleteSymbol) + result = safe_delete_tool.apply(name_path_pattern=name_path, relative_path=relative_path) + assert result == SUCCESS_RESULT, f"Expected successful deletion, but got: {result}" + + # verify the symbol was actually removed from the file + file_content = read_project_file(serena_agent.get_active_project(), relative_path) + assert name_path not in file_content, ( + f"Expected symbol {name_path} to be removed from {relative_path}, but it still appears in the file content" + ) From 6ef9158ff5bb23ddc694077ec040ef46eed8a6b4 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Tue, 31 Mar 2026 01:07:38 +0200 Subject: [PATCH 05/25] Add JetBrainsFindDeclarationTool --- src/serena/code_editor.py | 10 +++++ .../jetbrains/jetbrains_plugin_client.py | 23 +++++++++++ src/serena/tools/jetbrains_tools.py | 30 ++++++++++++++ src/serena/util/text_utils.py | 41 +++++++++++++++++++ 4 files changed, 104 insertions(+) diff --git a/src/serena/code_editor.py b/src/serena/code_editor.py index 430c8c2a..dfe4fe47 100644 --- a/src/serena/code_editor.py +++ b/src/serena/code_editor.py @@ -57,6 +57,16 @@ class CodeEditor(Generic[TSymbol], ABC): """ raise NotImplementedError("This method must be overridden for each subclass") + def read_file(self, relative_path: str) -> str: + """ + Reads the content of a file. + + :param relative_path: the relative path of the file to read + :return: the content of the file + """ + with self._open_file_context(relative_path) as file: + return file.get_contents() + @contextmanager def edited_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]: """ diff --git a/src/serena/jetbrains/jetbrains_plugin_client.py b/src/serena/jetbrains/jetbrains_plugin_client.py index b79b226a..52d21516 100644 --- a/src/serena/jetbrains/jetbrains_plugin_client.py +++ b/src/serena/jetbrains/jetbrains_plugin_client.py @@ -587,6 +587,29 @@ class JetBrainsPluginClient(ToStringMixin): } self._make_request("POST", "/refreshFile", request_data) + def find_declaration( + self, relative_path: str, line: int, col: int, include_body: bool, include_quick_info: bool + ) -> jb.SymbolCollectionResponse: + """ + Finds the declaration of the symbol at the given location. + + :param relative_path: the relative path to the file + :param line: the line number (0-based) + :param col: the column number (0-based) + :param include_body: whether to include the symbol body + :param include_quick_info: whether to include quick info about the symbol + """ + request_data = { + "relativePath": relative_path, + "line": line, + "col": col, + "includeBody": include_body, + "includeQuickInfo": include_quick_info, + } + symbol_collection = cast(jb.SymbolCollectionResponse, self._make_request("POST", "/findDeclaration", request_data)) + self._postprocess_symbol_collection_response(symbol_collection) + return symbol_collection + def close(self) -> None: self._session.close() diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index f96bcd67..7a943922 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -7,6 +7,7 @@ from serena.jetbrains.jetbrains_plugin_client import JetBrainsPluginClient from serena.jetbrains.jetbrains_types import SymbolDTO from serena.symbol import JetBrainsSymbolDictGrouper from serena.tools import Tool, ToolMarkerOptional, ToolMarkerSymbolicEdit, ToolMarkerSymbolicRead +from serena.util.text_utils import find_text_coordinates log = logging.getLogger(__name__) @@ -416,3 +417,32 @@ class JetBrainsTypeHierarchyTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptiona result = self._to_json(result_dict) return self._limit_length(result, max_answer_chars) + + +class JetBrainsFindDeclarationTool(Tool, ToolMarkerSymbolicRead): + """ + Finds the declaration of a symbol using the JetBrains backend + """ + + def apply(self, relative_path: str, regex: str, include_body: bool = False) -> str: + r""" + Finds the declaration of a symbol. + + :param relative_path: the relative path to the source file containing the symbol for which to find the declaration. + :param regex: a regular expression with one group, where the group matches the symbol for which to perform the lookup. + For example, to find the declaration of the `process` method in a call like `obj.process()`, + pass an expression like "obj\.(process)\(\)". + Provide as much context as necessary to render the regex unambiguous. + Uses Python syntax with MULTILINE and DOTALL flags enabled. + :param include_body: whether to include the symbol's body in the result. Default False. + """ + editor = self.create_code_editor() + content = editor.read_file(relative_path) + coords = find_text_coordinates(content, regex, require_unique=True) + assert coords is not None + with JetBrainsPluginClient.from_project(self.project) as client: + symbol_collection = client.find_declaration( + relative_path=relative_path, line=coords.line, col=coords.col, include_quick_info=False, include_body=include_body + ) + result = self._to_json(symbol_collection) + return result diff --git a/src/serena/util/text_utils.py b/src/serena/util/text_utils.py index e3c63e77..20c7b0b7 100644 --- a/src/serena/util/text_utils.py +++ b/src/serena/util/text_utils.py @@ -11,6 +11,7 @@ from bs4 import BeautifulSoup from joblib import Parallel, delayed from serena.constants import DEFAULT_SOURCE_FILE_ENCODING +from solidlsp.ls_utils import TextUtils log = logging.getLogger(__name__) @@ -523,3 +524,43 @@ class ContentReplacer: "Please revise the expression to be more specific or enable allow_multiple_occurrences if this is expected." ) return updated_content + + +@dataclass +class TextCoords: + line: int + """ + 0-based line number + """ + col: int + """ + 0-based column number + """ + + +def find_text_coordinates(content: str, regex: str, require_unique: bool = False) -> TextCoords | None: + """ + Finds the line and column number of the first match of a regex pattern in the given content. + + :param content: the text content to search through + :param regex: the regular expression pattern to search for; it must match part of a single line, + and contain exactly one group that captures the position of interest (e.g., the exact variable name to find the coordinates of) + :param require_unique: if True, raises an error if not exactly one match is found; + if False, returns None if no match is found, and returns the coordinates of the first match if multiple matches are found + :return: the coordinates of the match or None + """ + pattern = re.compile(regex, flags=re.MULTILINE | re.DOTALL) + matches = list(pattern.finditer(content)) + if len(matches) == 0: + if require_unique: + raise ValueError(f"No match found for regex: {regex}") + return None + else: + if require_unique and len(matches) > 1: + raise ValueError(f"Match must be unique; found {len(matches)} matches for regex: {regex}") + match = matches[0] + if len(match.groups()) != 1: + raise ValueError(f"Regex must contain exactly one group to capture the position, but found {len(match.groups())} groups.") + index_in_content = match.start(1) + line, col = TextUtils.get_line_col_from_index(content, index_in_content) + return TextCoords(line, col) From 213086b1626974a8921f3a71e801f3da124a316e Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Tue, 31 Mar 2026 01:22:00 +0200 Subject: [PATCH 06/25] Add JetBrainsFindImplementationsTool --- .../jetbrains/jetbrains_plugin_client.py | 17 ++++++++++++++ src/serena/tools/jetbrains_tools.py | 22 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/serena/jetbrains/jetbrains_plugin_client.py b/src/serena/jetbrains/jetbrains_plugin_client.py index 52d21516..cd6b3b8b 100644 --- a/src/serena/jetbrains/jetbrains_plugin_client.py +++ b/src/serena/jetbrains/jetbrains_plugin_client.py @@ -610,6 +610,23 @@ class JetBrainsPluginClient(ToStringMixin): self._postprocess_symbol_collection_response(symbol_collection) return symbol_collection + def find_implementations(self, relative_path: str, name_path: str, include_quick_info: bool) -> jb.SymbolCollectionResponse: + """ + Finds the implementations of a symbol. + + :param relative_path: the relative path to the file containing the symbol + :param name_path: the name path of the symbol + :param include_quick_info: whether to include quick info about the symbol + """ + request_data = { + "relativePath": relative_path, + "namePath": name_path, + "includeQuickInfo": include_quick_info, + } + symbol_collection = cast(jb.SymbolCollectionResponse, self._make_request("POST", "/findImplementations", request_data)) + self._postprocess_symbol_collection_response(symbol_collection) + return symbol_collection + def close(self) -> None: self._session.close() diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index 7a943922..33b4ac66 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -446,3 +446,25 @@ class JetBrainsFindDeclarationTool(Tool, ToolMarkerSymbolicRead): ) result = self._to_json(symbol_collection) return result + + +class JetBrainsFindImplementationsTool(Tool, ToolMarkerSymbolicRead): + """ + Finds the implementations of a symbol using the JetBrains backend + """ + + def apply(self, relative_path: str, name_path: str) -> str: + """ + Finds the implementations of a symbol. + + :param relative_path: the relative path to the source file containing the symbol for which to find implementations. + :param name_path: name path of the symbol for which to find implementations + """ + with JetBrainsPluginClient.from_project(self.project) as client: + symbol_collection = client.find_implementations( + relative_path=relative_path, + name_path=name_path, + include_quick_info=False, + ) + result = self._to_json(symbol_collection) + return result From 10307f045fa5e6ce2c5aff191411d4eec65a3990 Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Tue, 31 Mar 2026 15:54:11 +0200 Subject: [PATCH 07/25] Permit rename of dirs/files in JB world, WIP --- src/serena/code_editor.py | 23 +++++++++++-------- .../jetbrains/jetbrains_plugin_client.py | 2 +- src/serena/tools/symbol_tools.py | 11 +++++---- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/serena/code_editor.py b/src/serena/code_editor.py index dfe4fe47..202efe0f 100644 --- a/src/serena/code_editor.py +++ b/src/serena/code_editor.py @@ -230,13 +230,14 @@ class CodeEditor(Generic[TSymbol], ABC): edited_file.delete_text_between_positions(start_pos, end_pos) @abstractmethod - def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> str: + def rename_symbol(self, name_path: str | None, relative_path: str, new_name: str) -> str: """ - Renames the symbol with the given name throughout the codebase. + Renames a symbol, file, or directory throughout the codebase. :param name_path: the name path of the symbol to rename - :param relative_file_path: the relative path of the file containing the symbol - :param new_name: the new name for the symbol + :param relative_path: if `name_path` is passed, the relative path of the file containing the symbol. + Otherwise, the path to the directory or file to rename. + :param new_name: the new name :return: a status message """ @@ -347,8 +348,10 @@ class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]): operation.apply() return len(operations) - def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> str: - symbol = self._find_unique_symbol(name_path, relative_file_path) + def rename_symbol(self, name_path: str | None, relative_path: str, new_name: str) -> str: + if name_path is None: + raise NotImplementedError("Renaming files/directories is not yet implemented for in the language server backend.") + symbol = self._find_unique_symbol(name_path, relative_path) if not symbol.location.has_position_in_file(): raise ValueError(f"Symbol '{name_path}' does not have a valid position in file for renaming") @@ -356,9 +359,9 @@ class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]): assert symbol.location.line is not None assert symbol.location.column is not None - lang_server = self._get_language_server(relative_file_path) + lang_server = self._get_language_server(relative_path) rename_result = lang_server.request_rename_symbol_edit( - relative_file_path=relative_file_path, line=symbol.location.line, column=symbol.location.column, new_name=new_name + relative_file_path=relative_path, line=symbol.location.line, column=symbol.location.column, new_name=new_name ) if rename_result is None: raise ValueError( @@ -425,11 +428,11 @@ class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]): ) return JetBrainsSymbol(symbols[0], self._project) - def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> str: + def rename_symbol(self, name_path: str | None, relative_path: str, new_name: str) -> str: with JetBrainsPluginClient.from_project(self._project) as client: client.rename_symbol( name_path=name_path, - relative_path=relative_file_path, + relative_path=relative_path, new_name=new_name, rename_in_comments=False, rename_in_text_occurrences=False, diff --git a/src/serena/jetbrains/jetbrains_plugin_client.py b/src/serena/jetbrains/jetbrains_plugin_client.py index cd6b3b8b..f3ed6dc9 100644 --- a/src/serena/jetbrains/jetbrains_plugin_client.py +++ b/src/serena/jetbrains/jetbrains_plugin_client.py @@ -556,7 +556,7 @@ class JetBrainsPluginClient(ToStringMixin): return self._make_request("POST", "/inlineSymbol", request_data) def rename_symbol( - self, name_path: str, relative_path: str, new_name: str, rename_in_comments: bool, rename_in_text_occurrences: bool + self, name_path: str | None, relative_path: str, new_name: str, rename_in_comments: bool, rename_in_text_occurrences: bool ) -> None: """ Renames a symbol. diff --git a/src/serena/tools/symbol_tools.py b/src/serena/tools/symbol_tools.py index 2cf5c178..e17b74a0 100644 --- a/src/serena/tools/symbol_tools.py +++ b/src/serena/tools/symbol_tools.py @@ -408,27 +408,28 @@ class InsertBeforeSymbolTool(Tool, ToolMarkerSymbolicEdit): class RenameSymbolTool(Tool, ToolMarkerSymbolicEdit): """ - Renames a symbol throughout the codebase using language server refactoring capabilities. + Renames a symbol, file, or directory throughout the codebase using language server refactoring capabilities. """ def apply( self, - name_path: str, relative_path: str, new_name: str, + name_path: str | None = None, ) -> str: """ - Renames the symbol with the given `name_path` to `new_name` throughout the entire codebase. + Renames the symbol with the given `name_path` or a file/directory (if no `name_path` is passed) + to `new_name` throughout the entire codebase. Note: for languages with method overloading, like Java, name_path may have to include a method's signature to uniquely identify a method. - :param name_path: name path of the symbol to rename (definitions in the `find_symbol` tool apply) :param relative_path: the relative path to the file containing the symbol to rename :param new_name: the new name for the symbol + :param name_path: name path of the symbol to rename (definitions in the `find_symbol` tool apply) :return: result summary indicating success or failure """ code_editor = self.create_code_editor() - status_message = code_editor.rename_symbol(name_path, relative_file_path=relative_path, new_name=new_name) + status_message = code_editor.rename_symbol(name_path, relative_path=relative_path, new_name=new_name) return status_message From e6c97b99deca67cfe2551046c868a8f08255356e Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Wed, 1 Apr 2026 12:13:11 +0200 Subject: [PATCH 08/25] Split off JBRenameTool from the RenameSymbolTool The JB variant has more options, the difference to the LS variant will likely grow in the future --- src/serena/code_editor.py | 33 +++++++++++-------- .../config/internal_modes/jetbrains.yml | 2 ++ src/serena/tools/jetbrains_tools.py | 17 ++++++++++ src/serena/tools/symbol_tools.py | 12 +++---- 4 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/serena/code_editor.py b/src/serena/code_editor.py index 202efe0f..da397dcb 100644 --- a/src/serena/code_editor.py +++ b/src/serena/code_editor.py @@ -230,16 +230,8 @@ class CodeEditor(Generic[TSymbol], ABC): edited_file.delete_text_between_positions(start_pos, end_pos) @abstractmethod - def rename_symbol(self, name_path: str | None, relative_path: str, new_name: str) -> str: - """ - Renames a symbol, file, or directory throughout the codebase. - - :param name_path: the name path of the symbol to rename - :param relative_path: if `name_path` is passed, the relative path of the file containing the symbol. - Otherwise, the path to the directory or file to rename. - :param new_name: the new name - :return: a status message - """ + def rename_symbol(self, name_path: str, relative_path: str, new_name: str) -> str: + pass class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]): @@ -348,9 +340,15 @@ class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]): operation.apply() return len(operations) - def rename_symbol(self, name_path: str | None, relative_path: str, new_name: str) -> str: - if name_path is None: - raise NotImplementedError("Renaming files/directories is not yet implemented for in the language server backend.") + def rename_symbol(self, name_path: str, relative_path: str, new_name: str) -> str: + """ + Renames a symbol, file, or directory throughout the codebase. + + :param name_path: the name path of the symbol to rename + :param relative_path: the relative path of the file containing the symbol. + :param new_name: the new name + :return: a status message + """ symbol = self._find_unique_symbol(name_path, relative_path) if not symbol.location.has_position_in_file(): raise ValueError(f"Symbol '{name_path}' does not have a valid position in file for renaming") @@ -429,6 +427,15 @@ class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]): return JetBrainsSymbol(symbols[0], self._project) def rename_symbol(self, name_path: str | None, relative_path: str, new_name: str) -> str: + """ + Renames a code symbol, file, or directory throughout the codebase. + + :param name_path: the name path of the symbol to rename. Set to None for renaming a file or directory. + :param relative_path: if `name_path` is passed, the relative path of the file containing the symbol. + Otherwise, the path to the directory or file to rename. + :param new_name: the new name + :return: a status message + """ with JetBrainsPluginClient.from_project(self._project) as client: client.rename_symbol( name_path=name_path, diff --git a/src/serena/resources/config/internal_modes/jetbrains.yml b/src/serena/resources/config/internal_modes/jetbrains.yml index f9e60a4b..f7b2dd85 100644 --- a/src/serena/resources/config/internal_modes/jetbrains.yml +++ b/src/serena/resources/config/internal_modes/jetbrains.yml @@ -10,6 +10,7 @@ excluded_tools: - get_symbols_overview - restart_language_server - safe_delete_symbol + - rename_symbol included_optional_tools: - jet_brains_find_symbol - jet_brains_find_referencing_symbols @@ -18,3 +19,4 @@ included_optional_tools: - jet_brains_type_hierarchy - jet_brains_safe_delete - jet_brains_inline_symbol + - jet_brains_rename diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index 33b4ac66..afb70283 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -3,6 +3,7 @@ from collections import Counter from typing import Any, Literal import serena.jetbrains.jetbrains_types as jb +from serena.code_editor import JetBrainsCodeEditor from serena.jetbrains.jetbrains_plugin_client import JetBrainsPluginClient from serena.jetbrains.jetbrains_types import SymbolDTO from serena.symbol import JetBrainsSymbolDictGrouper @@ -468,3 +469,19 @@ class JetBrainsFindImplementationsTool(Tool, ToolMarkerSymbolicRead): ) result = self._to_json(symbol_collection) return result + + +class JetBrainsRenameTool(Tool, ToolMarkerSymbolicEdit): + def apply(self, relative_path: str, new_name: str, name_path: str | None = None) -> str: + """ + Renames a symbol, file, or directory throughout the codebase. + + :param relative_path: if `name_path` is passed, the relative path of the file containing the symbol. + Otherwise, the path to the directory or file to rename. + :param new_name: the new name + :param name_path: the name path of the symbol to rename or None if renaming a file or directory. + :return: a status message + """ + code_editor = JetBrainsCodeEditor(self.project) + result = code_editor.rename_symbol(name_path=name_path, relative_path=relative_path, new_name=new_name) + return self._to_json(result) diff --git a/src/serena/tools/symbol_tools.py b/src/serena/tools/symbol_tools.py index e17b74a0..bff6f2fd 100644 --- a/src/serena/tools/symbol_tools.py +++ b/src/serena/tools/symbol_tools.py @@ -408,27 +408,27 @@ class InsertBeforeSymbolTool(Tool, ToolMarkerSymbolicEdit): class RenameSymbolTool(Tool, ToolMarkerSymbolicEdit): """ - Renames a symbol, file, or directory throughout the codebase using language server refactoring capabilities. + Renames a symbol throughout the codebase using language server refactoring capabilities. + For JB, we use a separate tool. """ def apply( self, + name_path: str, relative_path: str, new_name: str, - name_path: str | None = None, ) -> str: """ - Renames the symbol with the given `name_path` or a file/directory (if no `name_path` is passed) - to `new_name` throughout the entire codebase. + Renames the symbol with the given `name_path` to `new_name` throughout the entire codebase. Note: for languages with method overloading, like Java, name_path may have to include a method's signature to uniquely identify a method. + :param name_path: name path of the symbol to rename (definitions in the `find_symbol` tool apply) :param relative_path: the relative path to the file containing the symbol to rename :param new_name: the new name for the symbol - :param name_path: name path of the symbol to rename (definitions in the `find_symbol` tool apply) :return: result summary indicating success or failure """ - code_editor = self.create_code_editor() + code_editor = self.create_ls_code_editor() status_message = code_editor.rename_symbol(name_path, relative_path=relative_path, new_name=new_name) return status_message From a45fe0cf1096c8a0f05f17c4752477e02a3f1f76 Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Thu, 2 Apr 2026 10:37:28 +0200 Subject: [PATCH 09/25] Exposed additional options in JBCodeEditor.rename_symbol [ci skip] Not forwarded to tool (yet) --- src/serena/code_editor.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/serena/code_editor.py b/src/serena/code_editor.py index da397dcb..2232f66b 100644 --- a/src/serena/code_editor.py +++ b/src/serena/code_editor.py @@ -426,7 +426,14 @@ class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]): ) return JetBrainsSymbol(symbols[0], self._project) - def rename_symbol(self, name_path: str | None, relative_path: str, new_name: str) -> str: + def rename_symbol( + self, + name_path: str | None, + relative_path: str, + new_name: str, + rename_in_comments: bool = False, + rename_in_text_occurrences: bool = False, + ) -> str: """ Renames a code symbol, file, or directory throughout the codebase. @@ -434,6 +441,8 @@ class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]): :param relative_path: if `name_path` is passed, the relative path of the file containing the symbol. Otherwise, the path to the directory or file to rename. :param new_name: the new name + :param rename_in_comments: whether to rename occurrences of the symbol in comments + :param rename_in_text_occurrences: whether to rename occurrences of the symbol in text :return: a status message """ with JetBrainsPluginClient.from_project(self._project) as client: @@ -441,7 +450,7 @@ class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]): name_path=name_path, relative_path=relative_path, new_name=new_name, - rename_in_comments=False, - rename_in_text_occurrences=False, + rename_in_comments=rename_in_comments, + rename_in_text_occurrences=rename_in_text_occurrences, ) return "Success" From 9f63606d7b80a635cc38f44ae882d50461cf0c7e Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Thu, 26 Mar 2026 10:45:10 +0100 Subject: [PATCH 10/25] Update README and About section with enhanced feature presentation for v1 --- README.md | 246 ++++++++++++-------------- docs/01-about/000_intro.md | 109 ++++++++++-- docs/02-usage/025_jetbrains_plugin.md | 9 +- 3 files changed, 213 insertions(+), 151 deletions(-) diff --git a/README.md b/README.md index 2fadd208..7d1d1ae1 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,121 @@ -

+

-* :rocket: Serena is a powerful **coding agent toolkit** capable of turning an LLM into a fully-featured agent that works **directly on your codebase**. - Unlike most other tools, it is not tied to an LLM, framework or an interface, making it easy to use it in a variety of ways. -* :wrench: Serena provides essential **semantic code retrieval and editing tools** that are akin to an IDE's capabilities, extracting code entities at the symbol level and exploiting relational structure. When combined with an existing coding agent, these tools greatly enhance (token) efficiency. -* :free: Serena is **free & open-source**, enhancing the capabilities of LLMs you already have access to free of charge. +

+ Serena is the IDE for your coding agent. + +

-You can think of Serena as providing IDE-like tools to your LLM/coding agent. -With it, the agent no longer needs to read entire files, perform grep-like searches or basic string replacements to find the right parts of the code and to edit code. -Instead, it can use code-centric tools like `find_symbol`, `find_referencing_symbols` and `insert_after_symbol`. +* Serena provides essential **semantic code retrieval, editing and refactoring tools** that are akin to an IDE's capabilities, + operating at the symbol level and exploiting relational structure. +* It integrates with any client/LLM via the model context protocol (**MCP**). -

- Serena is under active development! See the latest updates, upcoming features, and lessons learned to stay up to date. -

+Serena's **agent-first tool design** involves robust high-level abstractions, distinguishing it from +approaches that rely on low-level concepts like line numbers or primitive search patterns. -

- Changelog - Lessons Learned -

+Practically, this means that your agent operates **faster, more efficiently and more reliably**, especially in larger and +more complex codebases. -> [!TIP] -> The [**Serena JetBrains plugin**](#the-serena-jetbrains-plugin) has been released! +## Features -## LLM Integration +Serena supports two code intelligence backends: -Serena provides the necessary [tools](https://oraios.github.io/serena/01-about/035_tools.html) for coding workflows, but an LLM is required to do the actual work, -orchestrating tool use. +* **Language servers** implementing the language server protocol (LSP) — the free/open-source alternative. +* **The Serena JetBrains Plugin**, which leverages the capabilities of your JetBrains IDE. -In general, Serena can be integrated with an LLM in several ways: +Language Servers, while very powerful, have inherent limitations. +The JetBrains variant is the more advanced solution, as detailed below. It is also more memory-efficient, especially in +multi-agent scenarios. -* by using the **model context protocol (MCP)**. - Serena provides an MCP server which integrates with - * Claude Code and Claude Desktop, - * terminal-based clients like Codex, Gemini-CLI, Qwen3-Coder, rovodev, OpenHands CLI and others, - * IDEs like VSCode, Cursor or IntelliJ, - * Extensions like Cline or Roo Code - * Local clients like [OpenWebUI](https://docs.openwebui.com/openapi-servers/mcp), [Jan](https://jan.ai/docs/mcp-examples/browser/browserbase#enable-mcp), [Agno](https://docs.agno.com/introduction/playground) and others -* by using [mcpo to connect it to ChatGPT](docs/03-special-guides/serena_on_chatgpt.md) or other clients that don't support MCP but do support tool calling via OpenAPI. -* by incorporating Serena's tools into an agent framework of your choice, as illustrated [here](docs/03-special-guides/custom_agent.md). - Serena's tool implementation is decoupled from the framework-specific code and can thus easily be adapted to any agent framework. +### Language Support + +#### Language Servers + +When using Serena's language server backend, we provide **support for over 40 programming languages**, including +AL, Ansible, Bash, C#, C/C++, Clojure, Dart, Elixir, Elm, Erlang, Fortran, F# (currently with some bugs), GLSL, Go, Groovy (partial support), Haskell, HLSL, Java, Javascript, Julia, Kotlin, Lean 4, Lua, Luau, Markdown, MATLAB, Nix, OCaml, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, Solidity, Swift, TOML, TypeScript, WGSL, YAML, and Zig. + +#### The Serena JetBrains Plugin + +The [Serena JetBrains Plugin](https://plugins.jetbrains.com/plugin/28946-serena/) +leverages the powerful code analysis capabilities of your JetBrains IDE. +The plugin naturally supports all programming languages and frameworks that are supported by JetBrains IDEs, +including IntelliJ IDEA, PyCharm, Android Studio, WebStorm, PhpStorm, RubyMine, GoLand, and potentially others (Rider and CLion are unsupported though). + + + +See our [documentation page](https://oraios.github.io/serena/02-usage/025_jetbrains_plugin.html) for further details and instructions on how to apply the plugin. + +### Retrieval + +Serena's retrieval tools allow agents to explore codebases at the symbol level, understanding structure and relationships +without reading entire files. + +| Capability | Language Servers | JetBrains Plugin | +|----------------------------------|------------------|------------------| +| find symbol | yes | yes | +| symbol overview (file outline) | yes | yes | +| find referencing symbols | yes | yes | +| search in project dependencies | -- | yes | +| type hierarchy | -- | yes | +| find declaration | -- | yes | +| find implementations | -- | yes | +| query external projects | yes | yes | + +### Refactoring + +Without precise refactoring tools, agents are forced to resort to unreliable and expensive search and replace operations. + +| Capability | Language Servers | JetBrains Plugin | +|-------------------------------------------|--------------------|-----------------------------------| +| rename | yes (only symbols) | yes (symbols, files, directories) | +| move (symbol, file, directory) | -- | yes | +| inline | -- | yes | +| propagate deletions (remove unused code) | -- | yes | + +### Symbolic Editing + +Serena's symbolic editing tools are less error-prone and much more token-efficient than typical alternatives. + +| Capability | Language Servers | JetBrains Plugin | +|------------------------|-------------------|------------------| +| replace symbol body | yes | yes | +| insert after symbol | yes | yes | +| insert before symbol | yes | yes | +| safe delete | yes | yes | + +### 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, +since the surrounding harness already provides overlapping file, search, and shell capabilities. + +- **`search_for_pattern`** – flexible regex search across the codebase +- **`replace_content`** – agent-optimised regex-based and literal text replacement +- **`list_dir` / `find_file`** – directory listing and file search +- **`read_file`** – read files or file chunks +- **`execute_shell_command`** – run shell commands (e.g. builds, tests, linters) + +### Memory Management + +A memory system is elemental to long-lived agent workflows, especially when knowledge is to be shared across +sessions, users and projects. +Despite its simplicity, we received positive feedback from many users who tend to combine Serena's memory management system with their +agent's internal system (e.g., `AGENTS.md` files). +It can easily be disabled if you prefer to use something else. + +### Configurability + +Active tools, tool descriptions, prompts, language backend details and many other aspects of Serena +can be flexibly configured on a per-case basis by simply adjusting a few lines of YAML. +To achieve this, Serena offers multiple levels of (composable) configuration: + +* global configuration +* MCP launch command (CLI) configuration +* per-project configuration (with local overrides) +* execution context-specific configuration (e.g. for particular clients) +* dynamically composable configuration fragments (modes) ## Serena in Action @@ -57,51 +132,13 @@ Note how Serena's tools enable Claude to find and edit the right symbols. https://github.com/user-attachments/assets/6eaa9aa1-610d-4723-a2d6-bf1e487ba753 -## Programming Language Support & Semantic Analysis Capabilities - -Serena provides a set of versatile code querying and editing functionalities -based on symbolic understanding of the code. -Equipped with these capabilities, Serena discovers and edits code just like a seasoned developer -making use of an IDE's capabilities would. -Serena can efficiently find the right context and do the right thing even in very large and -complex projects! - -There are two alternative technologies powering these capabilities: - -* **Language servers** implementing the language server Protocol (LSP) — the free/open-source alternative. -* **The Serena JetBrains Plugin**, which leverages the powerful code analysis and editing - capabilities of your JetBrains IDE. - -You can choose either of these backends depending on your preferences and requirements. - -### Language Servers - -Serena incorporates a powerful abstraction layer for the integration of language servers -that implement the language server protocol (LSP). -The underlying language servers are typically open-source projects (like Serena) or at least freely available for use. - -With Serena's LSP library, we provide **support for over 40 programming languages**, including -AL, Ansible, Bash, C#, C/C++, Clojure, Dart, Elixir, Elm, Erlang, Fortran, F# (currently with some bugs), GLSL, Go, Groovy (partial support), Haskell, HLSL, Java, Javascript, Julia, Kotlin, Lean 4, Lua, Luau, Markdown, MATLAB, Nix, OCaml, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, Solidity, Swift, TOML, TypeScript, WGSL, YAML, and Zig. - -> [!IMPORTANT] -> Some language servers require additional dependencies to be installed; see the [Language Support](https://oraios.github.io/serena/01-about/020_programming-languages.html) page for details. - -### The Serena JetBrains Plugin - -As an alternative to language servers, the [Serena JetBrains Plugin](https://plugins.jetbrains.com/plugin/28946-serena/) -leverages the powerful code analysis capabilities of your JetBrains IDE. -The plugin naturally supports all programming languages and frameworks that are supported by JetBrains IDEs, -including IntelliJ IDEA, PyCharm, Android Studio, WebStorm, PhpStorm, RubyMine, GoLand, and potentially others (Rider and CLion are unsupported though). - - - -The plugin offers the most robust and most powerful Serena experience. -See our [documentation page](https://oraios.github.io/serena/02-usage/025_jetbrains_plugin.html) for further details and instructions. - ## Quick Start **Prerequisites**. Serena is managed by *uv*. If you don’t already have it, you need to [install uv](https://docs.astral.sh/uv/getting-started/installation/) before proceeding. +> [!NOTE] +> Some language servers require additional dependencies to be installed; see the [Language Support](https://oraios.github.io/serena/01-about/020_programming-languages.html) page for details. + **Starting the MCP Server**. The easiest way to start the Serena MCP server is by running the latest version from GitHub using uvx. Issue this command to see available options: @@ -124,74 +161,9 @@ Follow the link for specific instructions on how to set up Serena for Claude Cod Please refer to the [user guide](https://oraios.github.io/serena/02-usage/000_intro.html) for detailed instructions on how to use Serena effectively. -## Community Feedback - -Most users report that Serena has strong positive effects on the results of their coding agents, even when used within -very capable agents like Claude Code. Serena is often described to be a [game changer](https://www.reddit.com/r/ClaudeAI/comments/1lfsdll/try_out_serena_mcp_thank_me_later/), providing an enormous [productivity boost](https://www.reddit.com/r/ClaudeCode/comments/1mguoia/absolutely_insane_improvement_of_claude_code). - -Serena excels at navigating and manipulating complex codebases, providing tools that support precise code retrieval and editing in the presence of large, strongly structured codebases. -However, when dealing with tasks that involve only very few/small files, you may not benefit from including Serena on top of your existing coding agent. -In particular, when writing code from scratch, Serena will not provide much value initially, as the more complex structures that Serena handles more gracefully than simplistic, file-based approaches are yet to be created. - -Several videos and blog posts have talked about Serena: - -* YouTube: - * [AI Labs](https://www.youtube.com/watch?v=wYWyJNs1HVk&t=1s) - * [Yo Van Eyck](https://www.youtube.com/watch?v=UqfxuQKuMo8&t=45s) - * [JeredBlu](https://www.youtube.com/watch?v=fzPnM3ySmjE&t=32s) - -* Blog posts: - * [Serena's Design Principles](https://medium.com/@souradip1000/deconstructing-serenas-mcp-powered-semantic-code-understanding-architecture-75802515d116) - * [Serena with Claude Code (in Japanese)](https://blog.lai.so/serena/) - * [Turning Claude Code into a Development Powerhouse](https://robertmarshall.dev/blog/turning-claude-code-into-a-development-powerhouse/) - ## Acknowledgements -### Sponsors - -We are very grateful to our [sponsors](https://github.com/sponsors/oraios) who help us drive Serena's development. The core team -(the founders of [Oraios AI](https://oraios-ai.de/)) put in a lot of work in order to turn Serena into a useful open source project. -So far, there is no business model behind this project, and sponsors are our only source of income from it. - -Sponsors help us dedicating more time to the project, managing contributions, and working on larger features (like better tooling based on more advanced -LSP features, VSCode integration, debugging via the DAP, and several others). -If you find this project useful to your work, or would like to accelerate the development of Serena, consider becoming a sponsor. - -We are proud to announce that the Visual Studio Code team, together with Microsoft’s Open Source Programs Office and GitHub Open Source -have decided to sponsor Serena with a one-time contribution! - -

- Visual Studio Code sponsor logo -

- -### Community Contributions - A significant part of Serena, especially support for various languages, was contributed by the open source community. We are very grateful for the many contributors who made this possible and who played an important role in making Serena what it is today. -### Technologies -We built Serena on top of multiple existing open-source technologies, the most important ones being: - -1. [multilspy](https://github.com/microsoft/multilspy). - A library which wraps language server implementations and adapts them for interaction via Python. - It provided the basis for our library Solid-LSP (`src/solidlsp`). - Solid-LSP provides pure synchronous LSP calls and extends the original library with the symbolic logic - that Serena required. -2. [Python MCP SDK](https://github.com/modelcontextprotocol/python-sdk) -3. All the language servers that we use through Solid-LSP. - -Without these projects, Serena would not have been possible (or would have been significantly more difficult to build). - -## Customizing and Extending Serena - -It is straightforward to extend Serena's AI functionality with your own ideas. -Simply implement a new tool by subclassing -`serena.agent.Tool` and implement the `apply` method with a signature -that matches the tool's requirements. -Once implemented, `SerenaAgent` will automatically have access to the new tool. - -It is also relatively straightforward to add [support for a new programming language](/.serena/memories/adding_new_language_support_guide.md). - -We look forward to seeing what the community will come up with! -For details on contributing, see [contributing guidelines](/CONTRIBUTING.md). diff --git a/docs/01-about/000_intro.md b/docs/01-about/000_intro.md index 3fbd8b64..c1b9de6b 100644 --- a/docs/01-about/000_intro.md +++ b/docs/01-about/000_intro.md @@ -1,14 +1,101 @@ # About Serena -* Serena is a powerful **coding agent toolkit** capable of turning an LLM into a fully-featured agent that works - **directly on your codebase**. - Unlike most other tools, it is not tied to an LLM, framework or an interface, making it easy to use it in a variety of ways. -* Serena provides essential **semantic code retrieval and editing tools** that are akin to an IDE's capabilities, - extracting code entities at the symbol level and exploiting relational structure. - When combined with an existing coding agent, these tools greatly enhance (token) efficiency. -* Serena is **free & open-source**, enhancing the capabilities of LLMs you already have access to free of charge. +Serena is **the IDE for your coding agent**. -Therefore, you can think of Serena as providing IDE-like tools to your LLM/coding agent. -With it, the agent no longer needs to read entire files, perform grep-like searches or string replacements to find and -edit the right code. -Instead, it can use code-centred tools like `find_symbol`, `find_referencing_symbols` and `insert_after_symbol`. +* It provides essential **semantic code retrieval, editing and refactoring tools** that are akin to an IDE's capabilities, + operating at the symbol level and exploiting relational structure. +* It integrates with any client/LLM via the model context protocol (MCP). + +Serena's agent-first tool design involves robust high-level abstractions, distinguishing it from +approaches that rely on low-level concepts like line numbers or primitive search patterns. + +Practically, this means that your agent operates faster, more efficiently and more reliably, especially in larger and +more complex codebases. + +## Features + +Serena supports two code intelligence backends: + +* **Language servers** implementing the language server protocol (LSP) — the free/open-source alternative. +* **The Serena JetBrains Plugin**, which leverages the capabilities of your JetBrains IDE. + +Language Servers, while very powerful, have inherent limitations. +The JetBrains variant is the more advanced solution, as detailed below. It is also more memory-efficient, especially in +multi-agent scenarios. + + +Purchasing the JetBrains plugin supports the continued development of Serena. + + +### Retrieval + +Serena's retrieval tools allow agents to explore codebases at the symbol level, understanding structure and relationships +without reading entire files. + +| Capability | Language Server | JetBrains Plugin | +|------------------------------------|-----------------|------------------| +| **find symbol** | yes | yes | +| **symbol overview** (file outline) | yes | yes | +| **find referencing symbols** | yes | yes | +| **search in project dependencies** | -- | yes | +| **type hierarchy** | -- | yes | +| **find declaration** | -- | yes | +| **find implementations** | -- | yes | +| **query external projects** | yes | yes | + + +### Refactoring + +| Capability | Language Server | JetBrains Plugin | +|----------------------------------------------|--------------------|-----------------------------------| +| **rename** | yes (only symbols) | yes (symbols, files, directories) | +| **move** (symbol, file, directory) | -- | yes | +| **inline** | -- | yes | +| **propagate deletions** (remove unused code) | -- | yes | + + +### Symbolic Editing + +Much more token-efficient than default editing tools. + +| Capability | Language Server | JetBrains Plugin | +|--------------------------|-----------------|------------------| +| **replace symbol body** | yes | yes | +| **insert after symbol** | yes | yes | +| **insert before symbol** | yes | yes | +| **safe delete** | yes | yes | + +### 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, +since the surrounding harness already provides overlapping file, search, and shell capabilities. + +- **`search_for_pattern`** -- Flexible regex search across the codebase with glob-based file filtering, context lines, + and the option to restrict to code files only. Useful when you don't know the exact symbol name. +- **`list_dir` / `find_file`** -- Directory listing and file search with glob support. Helps agents orient themselves + in unfamiliar projects. +- **`read_file`** -- Read files or file chunks by line range, for cases where symbolic access isn't applicable + (e.g. configuration files, HTML templates). +- **`execute_shell_command`** -- Run shell commands (builds, tests, linters) directly from the agent, + with configurable working directory and output limits. + +### Memory Management + +Serena provides the functionality of a fully featured agent, and a useful aspect of this is Serena's memory system. +Despite its simplicity, we received positive feedback from many users who tend to combine it with their +agent's internal memory management (e.g., `AGENTS.md` files). +Many other memory management systems for agents exist, and you can easily disable Serena's memory management if you +prefer not to use it. + +## Configurability + +Active tools, tool descriptions, prompts, language backend details and many other aspects of Serena +can be flexibly configured on a per-case basis by simply adjusting a few lines of YAML. +To achieve this, Serena offers multiple levels of (composable) configuration: + +* global configuration +* MCP launch command (CLI) configuration +* per-project configuration (with local overrides) +* execution context-specific configuration (e.g. for particular clients) +* dynamically composable configuration fragments (modes) diff --git a/docs/02-usage/025_jetbrains_plugin.md b/docs/02-usage/025_jetbrains_plugin.md index 32c69591..3f51021e 100644 --- a/docs/02-usage/025_jetbrains_plugin.md +++ b/docs/02-usage/025_jetbrains_plugin.md @@ -23,10 +23,13 @@ The proceeds from plugin sales allow us to dedicate more resources to further de There are multiple features that are only available when using the JetBrains plugin: * **External library indexing**: Dependencies and libraries are fully indexed and accessible to Serena -* **No additional setup**: No need to download or configure separate language servers +* **Enhanced retrieval & refactoring capabilities**: The plugin supports additional tools (e.g. type + hierarchy retrieval, move, find declaration, inline symbol, etc.) +* **Improved multi-agent support**: A single IDE instance naturally serves arbitrarily many agent sessions * **Enhanced performance**: Faster tool execution thanks to optimized IDE integration -* **Multi-language excellence**: First-class support for polyglot projects with multiple languages and frameworks -* **Enhanced retrieval capabilities**: The plugin supports additional retrieval tools for type hierarchy information as well as fast and reliable documentation/type signature retrieval +* **Multi-language excellence** and **framework support**: First-class support for polyglot projects with multiple languages + and frameworks (whatever is recognised by your IDE as a symbol will also be available to Serena) +* **No additional setup**: No need to download or configure separate language servers We are also working on additional features like a `move_symbol` tool and debugging-related capabilities that will be available exclusively through the JetBrains plugin. From 2d78040cf928d9531087a521094a996632b0c7bf Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 2 Apr 2026 18:20:45 +0200 Subject: [PATCH 11/25] Auto-generate docs pages from README: About & Features --- README.md | 1 - docs/01-about/.gitignore | 5 +- docs/01-about/000_intro.md | 101 ------------------------------------- docs/autogen_docs.py | 35 +++++++++++++ 4 files changed, 39 insertions(+), 103 deletions(-) delete mode 100644 docs/01-about/000_intro.md diff --git a/README.md b/README.md index 7d1d1ae1..feba7f7b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@

Serena is the IDE for your coding agent. -

* Serena provides essential **semantic code retrieval, editing and refactoring tools** that are akin to an IDE's capabilities, diff --git a/docs/01-about/.gitignore b/docs/01-about/.gitignore index e1fbb867..463af074 100644 --- a/docs/01-about/.gitignore +++ b/docs/01-about/.gitignore @@ -1 +1,4 @@ -/035_tools.md \ No newline at end of file +# auto-generated files +/000_intro.md +/005_features.md +/035_tools.md diff --git a/docs/01-about/000_intro.md b/docs/01-about/000_intro.md deleted file mode 100644 index c1b9de6b..00000000 --- a/docs/01-about/000_intro.md +++ /dev/null @@ -1,101 +0,0 @@ -# About Serena - -Serena is **the IDE for your coding agent**. - -* It provides essential **semantic code retrieval, editing and refactoring tools** that are akin to an IDE's capabilities, - operating at the symbol level and exploiting relational structure. -* It integrates with any client/LLM via the model context protocol (MCP). - -Serena's agent-first tool design involves robust high-level abstractions, distinguishing it from -approaches that rely on low-level concepts like line numbers or primitive search patterns. - -Practically, this means that your agent operates faster, more efficiently and more reliably, especially in larger and -more complex codebases. - -## Features - -Serena supports two code intelligence backends: - -* **Language servers** implementing the language server protocol (LSP) — the free/open-source alternative. -* **The Serena JetBrains Plugin**, which leverages the capabilities of your JetBrains IDE. - -Language Servers, while very powerful, have inherent limitations. -The JetBrains variant is the more advanced solution, as detailed below. It is also more memory-efficient, especially in -multi-agent scenarios. - - -Purchasing the JetBrains plugin supports the continued development of Serena. - - -### Retrieval - -Serena's retrieval tools allow agents to explore codebases at the symbol level, understanding structure and relationships -without reading entire files. - -| Capability | Language Server | JetBrains Plugin | -|------------------------------------|-----------------|------------------| -| **find symbol** | yes | yes | -| **symbol overview** (file outline) | yes | yes | -| **find referencing symbols** | yes | yes | -| **search in project dependencies** | -- | yes | -| **type hierarchy** | -- | yes | -| **find declaration** | -- | yes | -| **find implementations** | -- | yes | -| **query external projects** | yes | yes | - - -### Refactoring - -| Capability | Language Server | JetBrains Plugin | -|----------------------------------------------|--------------------|-----------------------------------| -| **rename** | yes (only symbols) | yes (symbols, files, directories) | -| **move** (symbol, file, directory) | -- | yes | -| **inline** | -- | yes | -| **propagate deletions** (remove unused code) | -- | yes | - - -### Symbolic Editing - -Much more token-efficient than default editing tools. - -| Capability | Language Server | JetBrains Plugin | -|--------------------------|-----------------|------------------| -| **replace symbol body** | yes | yes | -| **insert after symbol** | yes | yes | -| **insert before symbol** | yes | yes | -| **safe delete** | yes | yes | - -### 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, -since the surrounding harness already provides overlapping file, search, and shell capabilities. - -- **`search_for_pattern`** -- Flexible regex search across the codebase with glob-based file filtering, context lines, - and the option to restrict to code files only. Useful when you don't know the exact symbol name. -- **`list_dir` / `find_file`** -- Directory listing and file search with glob support. Helps agents orient themselves - in unfamiliar projects. -- **`read_file`** -- Read files or file chunks by line range, for cases where symbolic access isn't applicable - (e.g. configuration files, HTML templates). -- **`execute_shell_command`** -- Run shell commands (builds, tests, linters) directly from the agent, - with configurable working directory and output limits. - -### Memory Management - -Serena provides the functionality of a fully featured agent, and a useful aspect of this is Serena's memory system. -Despite its simplicity, we received positive feedback from many users who tend to combine it with their -agent's internal memory management (e.g., `AGENTS.md` files). -Many other memory management systems for agents exist, and you can easily disable Serena's memory management if you -prefer not to use it. - -## Configurability - -Active tools, tool descriptions, prompts, language backend details and many other aspects of Serena -can be flexibly configured on a per-case basis by simply adjusting a few lines of YAML. -To achieve this, Serena offers multiple levels of (composable) configuration: - -* global configuration -* MCP launch command (CLI) configuration -* per-project configuration (with local overrides) -* execution context-specific configuration (e.g. for particular clients) -* dynamically composable configuration fragments (modes) diff --git a/docs/autogen_docs.py b/docs/autogen_docs.py index 5b4ca422..8c828809 100644 --- a/docs/autogen_docs.py +++ b/docs/autogen_docs.py @@ -1,5 +1,6 @@ import logging import os +import re import shutil from pathlib import Path from typing import Optional, List @@ -170,11 +171,45 @@ def autogen_tool_list(target_filename = "01-about/035_tools.md"): f.write(text.build()) +def autogen_about_intro_features(): + readme_path = Path(__file__).parent.parent / "README.md" + with open(readme_path, "r", encoding="utf-8") as f: + readme_contents = f.read() + + match = re.search(r"(.*?)(.*?)^(## Features.*?)^## ", readme_contents, re.DOTALL | re.MULTILINE) + assert match, f"Failed to extract about texts from README.md. {__file__} probably needs to be updated." + + tagline = match.group(1).strip() + about_text = match.group(2).strip() + features_text = match.group(3).strip() + + autogen_info = f"\n\n" + + with open(Path(__file__).parent / "01-about" / "000_intro.md", "w", encoding="utf-8") as f: + f.write(autogen_info) + f.write("# About Serena\n\n") + f.write(f"**{tagline}**\n\n") + f.write(f"{about_text}\n\n") + + jetbrains_marketplace_link = ('```{raw} html\n' + '

' + '' + '

\n```') + + with open(Path(__file__).parent / "01-about" / "005_features.md", "w", encoding="utf-8") as f: + f.write(autogen_info) + features_text = re.subn(r"^#", r"", features_text, flags=re.MULTILINE)[0] + features_text = re.sub(r' Date: Thu, 2 Apr 2026 21:02:04 +0200 Subject: [PATCH 12/25] Streamline 'Language Support' page, removing some overlap with other pages --- docs/01-about/020_programming-languages.md | 26 ++++++---------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/docs/01-about/020_programming-languages.md b/docs/01-about/020_programming-languages.md index 241e0b62..11585ea6 100644 --- a/docs/01-about/020_programming-languages.md +++ b/docs/01-about/020_programming-languages.md @@ -1,7 +1,7 @@ # Language Support Serena provides a set of versatile code querying and editing functionalities -based on symbolic understanding of the code. +based on symbolic understanding of the code across a wide range of programming languages. Equipped with these capabilities, Serena discovers and edits code just like a seasoned developer making use of an IDE's capabilities would. Serena can efficiently find the right context and do the right thing even in very large and @@ -13,7 +13,7 @@ There are two alternative technologies powering these capabilities: * **The Serena JetBrains Plugin**, which leverages the powerful code analysis and editing capabilities of your JetBrains IDE. -You can choose either of these backends depending on your preferences and requirements. +See the [Features](005_features) section for a detailed comparison of the capabilities provided by the JetBrains Plugin vs. language servers. ## Language Servers @@ -113,25 +113,15 @@ see Serena's [memory on that](https://github.com/oraios/serena/blob/main/.serena ## The Serena JetBrains Plugin -As an alternative to language servers, the [Serena JetBrains Plugin](https://plugins.jetbrains.com/plugin/28946-serena/) -leverages the powerful code analysis capabilities of JetBrains IDEs. -The plugin naturally supports all programming languages and frameworks that are supported by JetBrains IDEs, -including IntelliJ IDEA, PyCharm, Android Studio, WebStorm, PhpStorm, RubyMine, GoLand, and potentially others -(Rider and CLion are unsupported though). +The [Serena JetBrains Plugin](https://plugins.jetbrains.com/plugin/28946-serena/) leverages the powerful code analysis capabilities of JetBrains IDEs. +The plugin naturally supports all programming languages and frameworks that are supported by JetBrains IDEs. When using the plugin, Serena connects to an instance of your JetBrains IDE via the plugin. For users who already work in a JetBrains IDE, this means Serena seamlessly integrates with the IDE instance you typically have open anyway, -requiring no additional setup or configuration beyond the plugin itself. This approach offers several key advantages: +requiring no additional setup or configuration beyond the plugin itself. -* **External library indexing**: Dependencies and libraries are fully indexed and accessible to Serena -* **No additional setup**: No need to download or configure separate language servers -* **Enhanced performance**: Faster tool execution thanks to optimized IDE integration -* **Multi-language excellence**: First-class support for polyglot projects with multiple languages and frameworks - -Even if you prefer to work in a different code editor, you can still benefit from the JetBrains plugin by running -a JetBrains IDE instance (most have free community editions) alongside your preferred editor with your project -opened and indexed. Serena will connect to the IDE for code analysis while you continue working in your editor -of choice. +* See the [JetBrains Plugin documentation](../02-usage/025_jetbrains_plugin) for a high-level overview of its benefits and usage details. +* See the [Features](005_features) section for a detailed comparison of the capabilities provided by the JetBrains Plugin vs. language servers. ```{raw} html

@@ -140,5 +130,3 @@ of choice.

``` - -See the [JetBrains Plugin documentation](../02-usage/025_jetbrains_plugin) for usage details. From aa78d9751df1ee16a270d139c1f556f9f279729f Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 2 Apr 2026 21:17:23 +0200 Subject: [PATCH 13/25] Further improve JetBrains docs page for v1 --- docs/02-usage/025_jetbrains_plugin.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/docs/02-usage/025_jetbrains_plugin.md b/docs/02-usage/025_jetbrains_plugin.md index 3f51021e..b6da5037 100644 --- a/docs/02-usage/025_jetbrains_plugin.md +++ b/docs/02-usage/025_jetbrains_plugin.md @@ -17,23 +17,30 @@ especially for users of JetBrains IDEs. **Purchasing the JetBrains Plugin supports the Serena project.** The proceeds from plugin sales allow us to dedicate more resources to further developing and improving Serena. +How it works: + 1. Install the plugin in your JetBrains IDE + 2. Configure Serena to use the JetBrains language backend (see [below](configure-jetbrains)) + 3. Open the project you want to work on in your JetBrains IDE and activate it in Serena (see [below](jetbrains-workflow)) + 4. Start coding via your MCP client as usual ## Advantages of the JetBrains Plugin There are multiple features that are only available when using the JetBrains plugin: * **External library indexing**: Dependencies and libraries are fully indexed and accessible to Serena -* **Enhanced retrieval & refactoring capabilities**: The plugin supports additional tools (e.g. type - hierarchy retrieval, move, find declaration, inline symbol, etc.) -* **Improved multi-agent support**: A single IDE instance naturally serves arbitrarily many agent sessions -* **Enhanced performance**: Faster tool execution thanks to optimized IDE integration -* **Multi-language excellence** and **framework support**: First-class support for polyglot projects with multiple languages +* **Enhanced retrieval & refactoring capabilities**: The plugin adds additional [tools](../01-about/035_tools) (e.g. type + hierarchy retrieval, move, find declaration, inline symbol, etc.) + and transforms the underlying mechanisms of shared tools to build upon the IDE's capabilities. +* **Improved multi-agent support**: A single IDE instance naturally serves arbitrarily many agent sessions without requiring additional resources. +* **Enhanced performance**: Faster tool execution thanks to optimized IDE integration. +* **Multi-language excellence** and **framework support**: First-class support for polyglot projects with multiple languages. and frameworks (whatever is recognised by your IDE as a symbol will also be available to Serena) -* **No additional setup**: No need to download or configure separate language servers +* **No additional setup**: No need to download or configure separate language servers. -We are also working on additional features like a `move_symbol` tool and debugging-related capabilities that +We are also working on additional features like debugging and advanced introspection capabilities, which will be available exclusively through the JetBrains plugin. +(configure-jetbrains)= ## Configuring Serena to Use the JetBrains Plugin After installing the plugin, you need to configure Serena to use it. @@ -82,15 +89,16 @@ you will see `Languages: Using JetBrains backend` in the configuration overview. You will also notice that your client will use the JetBrains-specific tools like `jet_brains_find_symbol` and others like it. +(jetbrains-workflow)= ## Workflow Having installed the plugin in your IDE and having configured Serena to use the JetBrains backend, the general workflow is simple: 1. Open the project you want to work on in your JetBrains IDE -2. Open the project's root folder as a project in Serena (see [Project Creation](project-creation-indexing) and [Project Activation](project-activation)) +2. Activate the project's root folder as a project in Serena (see [Project Creation](project-creation-indexing) and [Project Activation](project-activation)) 3. Start using Serena tools as usual -Note that the folder that is open in your IDE and the project's root folder must match. +Note that the project folder that is open in your IDE and the Serena project root folder must match. :::{tip} If you need to work on multiple projects in the same agent session, create a monorepo folder From d214de40b4295ee438875ff2a5b23a0ebf052031 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 2 Apr 2026 21:43:13 +0200 Subject: [PATCH 14/25] Update JetBrains tools for v1 * Add missing descriptions * Add missing tool markers * Partly improve apply docstrings --- src/serena/tools/jetbrains_tools.py | 47 +++++++++++++++++------------ 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index afb70283..86f2b571 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -114,6 +114,10 @@ class JetBrainsFindSymbolTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional): class JetBrainsMoveSymbolTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): + """ + Moves a symbol, file or directory to a new location using the JetBrains backend, updating all references + """ + def apply( self, name_path: str | None = None, @@ -131,7 +135,6 @@ class JetBrainsMoveSymbolTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): If None, the symbol is moved to the top level of the target file. :param target_relative_path: the relative path to the target file. If None, the symbol is moved within the same file. - :return: JSON string with the result of the operation. """ with JetBrainsPluginClient.from_project(self.project) as client: response_dict = client.move_symbol( @@ -144,6 +147,10 @@ class JetBrainsMoveSymbolTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): class JetBrainsSafeDeleteTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): + """ + Safely deletes a symbol using the JetBrains backend, checking for remaining usages first + """ + def apply( self, relative_path: str, @@ -162,7 +169,6 @@ class JetBrainsSafeDeleteTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): Default is False (safe mode: will report usages instead of deleting). :param propagate: whether to propagate the deletion to usages of the symbol and also remove symbols that become unused after the deletion. Default is False. - :return: JSON string with the result of the operation. """ with JetBrainsPluginClient.from_project(self.project) as client: response_dict = client.safe_delete( @@ -175,6 +181,10 @@ class JetBrainsSafeDeleteTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): class JetBrainsInlineSymbol(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): + """ + Inlines a symbol using the JetBrains backend, replacing all call sites with the symbol's body + """ + def apply( self, name_path: str, @@ -182,16 +192,14 @@ class JetBrainsInlineSymbol(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): keep_definition: bool = False, ) -> str: """ - Inlines a symbol (usually method, but also classes may be amenable to inlining, + Inlines a symbol (usually a method/function, but also classes may be amenable to inlining, which turns invocation into anonymous class creation), - replacing all call sites with the method's body. - An error is raised when requested on a symbol that cannot be inlined. + replacing all call sites with the symbol's body. - :param name_path: the name path of the method to inline. - :param relative_path: the relative path to the file containing the method to inline. + :param name_path: the name path of the symbol to inline. + :param relative_path: the relative path to the file containing the symbol to inline. :param keep_definition: whether to keep the original method definition after inlining all call sites. - May be ignored in some cases (e.g., when inlining a class).. - :return: JSON string with the result of the operation. + May be ignored in some cases (e.g. when inlining a class). """ with JetBrainsPluginClient.from_project(self.project) as client: response_dict = client.inline_symbol( @@ -217,14 +225,12 @@ class JetBrainsFindReferencingSymbolsTool(Tool, ToolMarkerSymbolicRead, ToolMark max_answer_chars: int = -1, ) -> str: """ - Finds symbols that reference the symbol at the given `name_path`. + Finds symbols that reference the symbol at the specified symbol, i.e. returns symbols whose definitions (e.g. a function body) contain a reference to the given symbol. The result will contain metadata about the referencing symbols. - :param name_path: name path of the symbol for which to find references; matching logic as described in find symbol tool. - :param relative_path: the relative path to the file containing the symbol for which to find references. - Note that here you can't pass a directory but must pass a file. + :param name_path: name path of the symbol for which to find references + :param relative_path: the relative path to the file containing the symbol (must be a file, not a directory) :param max_answer_chars: max characters for the result (-1 for default). If exceeded, no content/a shortened result is returned. - :return: a list of JSON objects with the symbols referencing the requested symbol """ with JetBrainsPluginClient.from_project(self.project) as client: response_dict = client.find_references( @@ -279,7 +285,6 @@ class JetBrainsGetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOp :param depth: depth up to which descendants shall be retrieved (e.g., use 1 to also retrieve immediate children). :param max_answer_chars: max characters for the result (-1 for default). If exceeded, no content/a shortened result is returned. :param include_file_documentation: whether to include the file's docstring. Default False. - :return: a JSON object containing the symbols grouped by kind in a compact format. """ with JetBrainsPluginClient.from_project(self.project) as client: symbol_overview = client.get_symbols_overview( @@ -420,7 +425,7 @@ class JetBrainsTypeHierarchyTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptiona return self._limit_length(result, max_answer_chars) -class JetBrainsFindDeclarationTool(Tool, ToolMarkerSymbolicRead): +class JetBrainsFindDeclarationTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional): """ Finds the declaration of a symbol using the JetBrains backend """ @@ -449,7 +454,7 @@ class JetBrainsFindDeclarationTool(Tool, ToolMarkerSymbolicRead): return result -class JetBrainsFindImplementationsTool(Tool, ToolMarkerSymbolicRead): +class JetBrainsFindImplementationsTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional): """ Finds the implementations of a symbol using the JetBrains backend """ @@ -471,10 +476,14 @@ class JetBrainsFindImplementationsTool(Tool, ToolMarkerSymbolicRead): return result -class JetBrainsRenameTool(Tool, ToolMarkerSymbolicEdit): +class JetBrainsRenameTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): + """ + Renames a symbol, file or directory throughout the codebase using the JetBrains backend. + """ + def apply(self, relative_path: str, new_name: str, name_path: str | None = None) -> str: """ - Renames a symbol, file, or directory throughout the codebase. + Renames a symbol, file or directory throughout the codebase. :param relative_path: if `name_path` is passed, the relative path of the file containing the symbol. Otherwise, the path to the directory or file to rename. From efabaa634661864e7141df5c8783ed95a2eadf59 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 2 Apr 2026 22:17:21 +0200 Subject: [PATCH 15/25] InitialInstructionsTool: Shorten tool description (for auto-generated tools list) --- src/serena/tools/workflow_tools.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/serena/tools/workflow_tools.py b/src/serena/tools/workflow_tools.py index 158170a4..343f26e2 100644 --- a/src/serena/tools/workflow_tools.py +++ b/src/serena/tools/workflow_tools.py @@ -120,10 +120,8 @@ class PrepareForNewConversationTool(Tool): class InitialInstructionsTool(Tool, ToolMarkerDoesNotRequireActiveProject): """ - Provides instructions on how to use the Serena toolbox. - Should only be used in settings where the system prompt is not read automatically by the client. - - NOTE: Some MCP clients (including Claude Desktop) do not read the system prompt automatically! + Provides instructions Serena usage (i.e. the 'Serena Instructions Manual') + for clients that do not read the initial instructions when the MCP server is connected. """ def apply(self) -> str: From 0f479a0ee234efa9f1b058ce8231c723992b5b27 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Fri, 3 Apr 2026 11:46:21 +0200 Subject: [PATCH 16/25] Update instructions of JetBrainsMoveTool (and renamed to remove 'Symbol' from name) --- src/serena/tools/jetbrains_tools.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index 86f2b571..fde4aeee 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -113,28 +113,39 @@ class JetBrainsFindSymbolTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional): return self._limit_length(result, max_answer_chars, shortened_result_factories=[create_shortened_result]) -class JetBrainsMoveSymbolTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): +class JetBrainsMoveTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): """ Moves a symbol, file or directory to a new location using the JetBrains backend, updating all references """ def apply( self, - name_path: str | None = None, relative_path: str | None = None, - target_parent_name_path: str | None = None, + name_path: str | None = None, target_relative_path: str | None = None, + target_parent_name_path: str | None = None, ) -> str: """ - Moves a symbol to a different location (file or parent symbol) using the JetBrains refactoring engine. - References to the symbol are automatically updated. + Moves a symbol, file or directory to a different location. The target location is the new "parent" + of the symbol, i.e. the moved entity is never renamed by the operation, only moved. + References to affected symbols are automatically updated. + + Valid moves: + - Symbol: + * (relative_path, name_path) -> new parent symbol (relative path + name path) + * (relative_path, name_path) -> top level of target file or directory (target_relative_path) + Always consider the concrete language-specific semantics! + - file: valid for languages like Python, where files are modules + - directory: valid for languages like Java, where directories are packages and can contain classes + - File or directory: + * relative_path -> new parent directory (target_relative_path) - :param name_path: the name path of the symbol to move (e.g. "MyClass/my_method"). :param relative_path: the relative path to the file containing the symbol to move. - :param target_parent_name_path: the name path of the target parent symbol (e.g. "TargetClass"). - If None, the symbol is moved to the top level of the target file. + :param name_path: the name path of the symbol to move (e.g. "MyClass/my_method"). :param target_relative_path: the relative path to the target file. If None, the symbol is moved within the same file. + :param target_parent_name_path: the name path of the target parent symbol (e.g. "TargetClass"). + If None, the symbol is moved to the top level of the target file. """ with JetBrainsPluginClient.from_project(self.project) as client: response_dict = client.move_symbol( From 6870c520310bdab2796cfa6127441d1697221952 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Fri, 3 Apr 2026 11:45:51 +0200 Subject: [PATCH 17/25] Update 'jetbrains' mode definition --- .../resources/config/internal_modes/jetbrains.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/serena/resources/config/internal_modes/jetbrains.yml b/src/serena/resources/config/internal_modes/jetbrains.yml index f7b2dd85..a08b189f 100644 --- a/src/serena/resources/config/internal_modes/jetbrains.yml +++ b/src/serena/resources/config/internal_modes/jetbrains.yml @@ -1,6 +1,7 @@ description: JetBrains tools replace language server-based tools prompt: | - You have access to the very powerful JetBrains tools for symbolic operations: + You have access to the very powerful JetBrains tools for symbolic operations. + These party replace regular tools you were informed about: * `jet_brains_find_symbol` replaces `find_symbol` * `jet_brains_find_referencing_symbols` replaces `find_referencing_symbols` * `jet_brains_get_symbols_overview` replaces `get_symbols_overview` @@ -12,11 +13,13 @@ excluded_tools: - safe_delete_symbol - rename_symbol included_optional_tools: - - jet_brains_find_symbol + - jet_brains_find_declaration + - jet_brains_find_implementations - jet_brains_find_referencing_symbols + - jet_brains_find_symbol - jet_brains_get_symbols_overview - - jet_brains_move_symbol - - jet_brains_type_hierarchy - - jet_brains_safe_delete - jet_brains_inline_symbol + - jet_brains_move + - jet_brains_type_hierarchy - jet_brains_rename + - jet_brains_safe_delete From 1ad26d5af317abd568a0ae35d066aeccf4151b26 Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Fri, 3 Apr 2026 14:37:13 +0200 Subject: [PATCH 18/25] Fix: apply exclusions from base modes to exposed tools --- src/serena/agent.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index c5cec083..e73bd7a8 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -239,6 +239,13 @@ class ActiveModes: self._active_modes.append(mode) return self._active_modes + # TODO: apply caching like in get_modes + def get_default_modes(self) -> Sequence[SerenaAgentMode]: + return [SerenaAgentMode.load(mode_name) for mode_name in self._default_modes or []] + + def get_base_modes(self) -> Sequence[SerenaAgentMode]: + return [SerenaAgentMode.load(mode_name) for mode_name in self._base_modes or []] + class SerenaAgent: def __init__( @@ -371,7 +378,7 @@ class SerenaAgent: self.serena_config, self._language_backend, self._context, self._active_modes, self._active_project ) self._exposed_tools = self._base_toolset.to_available_tools(self._all_tools) - log.info(f"Number of exposed tools: {len(self._exposed_tools)}") + log.info(f"Number of exposed tools: {len(self._exposed_tools)}. Exposed tools: {self._exposed_tools.tool_names}") # update the active tools (considering the active project, if any) self._active_tools: AvailableTools @@ -427,11 +434,20 @@ class SerenaAgent: # consider modes # Since modes can be dynamically turned on and off, we don't include their definitions directly, - # but for the initially active modes, we make sure that the tools they enable are included. - for mode in modes.get_modes(): + # For the initially active dynamic modes, we make sure that the tools they enable are included. + for mode in modes.get_default_modes(): tool_inclusion_definitions.append( NamedToolInclusionDefinition( - name=f"InitialModeInclusions[{mode.name}]", included_optional_tools=mode.included_optional_tools + name=f"InitialDynamicModeInclusions[{mode.name}]", included_optional_tools=mode.included_optional_tools + ) + ) + # For the base modes, we also apply the tool exclusions, since they apply throughout the entire session + for base_mode in modes.get_base_modes(): + tool_inclusion_definitions.append( + NamedToolInclusionDefinition( + name=f"BaseMode[{base_mode.name}]", + included_optional_tools=base_mode.included_optional_tools, + excluded_tools=base_mode.excluded_tools, ) ) From da105faad160a73f3a4d6aa504fe3db0952bd40d Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Fri, 3 Apr 2026 14:59:19 +0200 Subject: [PATCH 19/25] Improved MoveTool description --- src/serena/tools/jetbrains_tools.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index fde4aeee..dc6f41a3 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -120,32 +120,30 @@ class JetBrainsMoveTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): def apply( self, - relative_path: str | None = None, + relative_path: str, name_path: str | None = None, target_relative_path: str | None = None, target_parent_name_path: str | None = None, ) -> str: """ - Moves a symbol, file or directory to a different location. The target location is the new "parent" + Moves a symbol, file or directory to a different location. The target location is the new parent of the symbol, i.e. the moved entity is never renamed by the operation, only moved. References to affected symbols are automatically updated. Valid moves: - Symbol: - * (relative_path, name_path) -> new parent symbol (relative path + name path) + * (relative_path, name_path) -> new parent symbol (target_relative_path, target_parent_name_path) * (relative_path, name_path) -> top level of target file or directory (target_relative_path) Always consider the concrete language-specific semantics! - - file: valid for languages like Python, where files are modules - - directory: valid for languages like Java, where directories are packages and can contain classes + - target is a file: valid for languages like Python, where files are modules + - target is a directory: valid for languages like Java, where directories are packages and can contain classes - File or directory: * relative_path -> new parent directory (target_relative_path) :param relative_path: the relative path to the file containing the symbol to move. - :param name_path: the name path of the symbol to move (e.g. "MyClass/my_method"). - :param target_relative_path: the relative path to the target file. If None, the symbol - is moved within the same file. - :param target_parent_name_path: the name path of the target parent symbol (e.g. "TargetClass"). - If None, the symbol is moved to the top level of the target file. + :param name_path: the name path of the symbol to move (empty for moving file or dir). + :param target_relative_path: the relative path of the target directory or file. + :param target_parent_name_path: the name path of the target parent symbol. """ with JetBrainsPluginClient.from_project(self.project) as client: response_dict = client.move_symbol( From c65b9aaaac200635e71680166c62a6d9d0331c48 Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Fri, 3 Apr 2026 15:21:32 +0200 Subject: [PATCH 20/25] Dashboard: enable text_select and zoom --- src/serena/dashboard.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/serena/dashboard.py b/src/serena/dashboard.py index 9a8f3f50..cf450891 100644 --- a/src/serena/dashboard.py +++ b/src/serena/dashboard.py @@ -742,6 +742,9 @@ class SerenaDashboardViewer: width=self.width, height=self.height, hidden=self.start_minimized, + text_select=True, + zoomable=True, + ) assert window is not None self.window = window From e39830df4d819822ed4967e4b25057d1508ff355 Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Fri, 3 Apr 2026 15:25:35 +0200 Subject: [PATCH 21/25] Minor improvement in find_declaration docstring --- src/serena/dashboard.py | 1 - src/serena/tools/jetbrains_tools.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/serena/dashboard.py b/src/serena/dashboard.py index cf450891..257b8540 100644 --- a/src/serena/dashboard.py +++ b/src/serena/dashboard.py @@ -744,7 +744,6 @@ class SerenaDashboardViewer: hidden=self.start_minimized, text_select=True, zoomable=True, - ) assert window is not None self.window = window diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index dc6f41a3..fdffa191 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -446,8 +446,8 @@ class JetBrainsFindDeclarationTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptio :param relative_path: the relative path to the source file containing the symbol for which to find the declaration. :param regex: a regular expression with one group, where the group matches the symbol for which to perform the lookup. For example, to find the declaration of the `process` method in a call like `obj.process()`, - pass an expression like "obj\.(process)\(\)". - Provide as much context as necessary to render the regex unambiguous. + pass an expression like "obj\.(process)\(process_input_arg=37\)". + Prefer regexes with sufficiently large context around the group to render the match unambiguous. Uses Python syntax with MULTILINE and DOTALL flags enabled. :param include_body: whether to include the symbol's body in the result. Default False. """ From 60067f327433c9a4a5f3897acf705299bfca13ca Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Fri, 3 Apr 2026 15:05:36 +0200 Subject: [PATCH 22/25] Mark some new tools as beta tools and denote in tool list --- docs/autogen_docs.py | 11 ++++++++--- src/serena/tools/jetbrains_tools.py | 8 ++++---- src/serena/tools/tools_base.py | 10 +++++++++- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/docs/autogen_docs.py b/docs/autogen_docs.py index 8c828809..6dfd37f0 100644 --- a/docs/autogen_docs.py +++ b/docs/autogen_docs.py @@ -153,9 +153,10 @@ def autogen_tool_list(target_filename = "01-about/035_tools.md"): with open(target_file, "w") as f: f.write("\n\n") f.write("# Tools\n\n") - f.write("Find the full list of Serena's tools below. \n") + f.write("Find the full list of Serena's tools below.\n\n") f.write("Note that in most configurations, only a subset of these tools will be enabled simultaneously.\n") - f.write("Tools marked as optional are disabled by default.\n\n") + f.write("Tools marked as *optional* are disabled by default.\n\n") + f.write("Tools marked as [BETA] were recently introduced and may not be fully robust yet.\n\n") tools_by_module = ToolRegistry().get_registered_tools_by_module() priority_modules = {"serena.tools.symbol_tools": 1, "serena.tools.jetbrains_tools": 2} @@ -166,7 +167,11 @@ def autogen_tool_list(target_filename = "01-about/035_tools.md"): module = module.replace("serena.tools.", "") text.with_line(f"* **{module}**") for tool in tools: - info = " *(optional)*" if tool.is_optional else "" + info = "" + if tool.is_optional: + info += " *(optional)*" + if tool.is_beta: + info += " [BETA]" text.with_line(f"* `{tool.tool_name}`{info}: {tool.class_docstring}", indent=2) f.write(text.build()) diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index fdffa191..1c4ef63f 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -7,7 +7,7 @@ from serena.code_editor import JetBrainsCodeEditor from serena.jetbrains.jetbrains_plugin_client import JetBrainsPluginClient from serena.jetbrains.jetbrains_types import SymbolDTO from serena.symbol import JetBrainsSymbolDictGrouper -from serena.tools import Tool, ToolMarkerOptional, ToolMarkerSymbolicEdit, ToolMarkerSymbolicRead +from serena.tools import Tool, ToolMarkerBeta, ToolMarkerOptional, ToolMarkerSymbolicEdit, ToolMarkerSymbolicRead from serena.util.text_utils import find_text_coordinates log = logging.getLogger(__name__) @@ -113,7 +113,7 @@ class JetBrainsFindSymbolTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional): return self._limit_length(result, max_answer_chars, shortened_result_factories=[create_shortened_result]) -class JetBrainsMoveTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): +class JetBrainsMoveTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional, ToolMarkerBeta): """ Moves a symbol, file or directory to a new location using the JetBrains backend, updating all references """ @@ -155,7 +155,7 @@ class JetBrainsMoveTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): return self._to_json(response_dict) -class JetBrainsSafeDeleteTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): +class JetBrainsSafeDeleteTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional, ToolMarkerBeta): """ Safely deletes a symbol using the JetBrains backend, checking for remaining usages first """ @@ -189,7 +189,7 @@ class JetBrainsSafeDeleteTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): return self._to_json(response_dict) -class JetBrainsInlineSymbol(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional): +class JetBrainsInlineSymbol(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional, ToolMarkerBeta): """ Inlines a symbol using the JetBrains backend, replacing all call sites with the symbol's body """ diff --git a/src/serena/tools/tools_base.py b/src/serena/tools/tools_base.py index 200c2ae3..cb2b299d 100644 --- a/src/serena/tools/tools_base.py +++ b/src/serena/tools/tools_base.py @@ -110,6 +110,12 @@ class ToolMarkerSymbolicEdit(ToolMarkerCanEdit): """ +class ToolMarkerBeta(ToolMarker): + """ + Marker for tools that are considered beta features (may not be fully robust) + """ + + class ApplyMethodProtocol(Protocol): """Callable protocol for the apply method of a tool.""" @@ -407,6 +413,7 @@ class EditedFileContext: class RegisteredTool: tool_class: type[Tool] is_optional: bool + is_beta: bool tool_name: str @property @@ -429,10 +436,11 @@ class ToolRegistry: if not any(cls.__module__.startswith(pkg) for pkg in tool_packages): continue is_optional = issubclass(cls, ToolMarkerOptional) + is_beta = issubclass(cls, ToolMarkerBeta) name = cls.get_name_from_cls() if name in self._tool_dict: raise ValueError(f"Duplicate tool name found: {name}. Tool classes must have unique names.") - self._tool_dict[name] = RegisteredTool(tool_class=cls, is_optional=is_optional, tool_name=name) + self._tool_dict[name] = RegisteredTool(tool_class=cls, is_optional=is_optional, tool_name=name, is_beta=is_beta) def get_registered_tools_by_module(self) -> dict[str, list[RegisteredTool]]: """ From 83bd14b38b1ce92a37970788e0e242e6c30eb551 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Fri, 3 Apr 2026 15:35:03 +0200 Subject: [PATCH 23/25] Mention beta features on JetBrains docs page --- docs/02-usage/025_jetbrains_plugin.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/02-usage/025_jetbrains_plugin.md b/docs/02-usage/025_jetbrains_plugin.md index b6da5037..c8af4a14 100644 --- a/docs/02-usage/025_jetbrains_plugin.md +++ b/docs/02-usage/025_jetbrains_plugin.md @@ -14,15 +14,15 @@ leverage the powerful code analysis and editing capabilities of your JetBrains I We recommend the JetBrains plugin as the preferred way of using Serena, especially for users of JetBrains IDEs. +How it works: +1. Install the plugin in your JetBrains IDE +2. Configure Serena to use the JetBrains language backend (see [below](configure-jetbrains)) +3. Open the project you want to work on in your JetBrains IDE and activate it in Serena (see [below](jetbrains-workflow)) +4. Start coding via your MCP client as usual + **Purchasing the JetBrains Plugin supports the Serena project.** The proceeds from plugin sales allow us to dedicate more resources to further developing and improving Serena. -How it works: - 1. Install the plugin in your JetBrains IDE - 2. Configure Serena to use the JetBrains language backend (see [below](configure-jetbrains)) - 3. Open the project you want to work on in your JetBrains IDE and activate it in Serena (see [below](jetbrains-workflow)) - 4. Start coding via your MCP client as usual - ## Advantages of the JetBrains Plugin There are multiple features that are only available when using the JetBrains plugin: @@ -40,6 +40,12 @@ There are multiple features that are only available when using the JetBrains plu We are also working on additional features like debugging and advanced introspection capabilities, which will be available exclusively through the JetBrains plugin. +:::{note} +With Serena's JetBrains tools, we try to offer the latest features. +As a result, some of them are considered as beta features (see [tool list](../01-about/035_tools)), which may have some quirks. +Please report your experience with these tools if they do not work as expected. +::: + (configure-jetbrains)= ## Configuring Serena to Use the JetBrains Plugin From 827f98fde986075b5148a381c46f02e98ee99000 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Fri, 3 Apr 2026 16:06:20 +0200 Subject: [PATCH 24/25] Add news item for v1 --- .../resources/dashboard/news/20260404.html | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/serena/resources/dashboard/news/20260404.html diff --git a/src/serena/resources/dashboard/news/20260404.html b/src/serena/resources/dashboard/news/20260404.html new file mode 100644 index 00000000..debdfb63 --- /dev/null +++ b/src/serena/resources/dashboard/news/20260404.html @@ -0,0 +1,23 @@ +
+

Serena v1.0 Is Here!

+

April 3, 2026

+

+ After a full year of work since the first version of Serena, we are excited to announce the release of Serena v1.0! +

+

+ Since our last update, we have now added support for exciting new retrieval and refactoring tools, especially for users of the JetBrains backend. +

+
    +
  • safe_delete, jet_brains_safe_delete (beta):
    Safely deletes a symbol, checking for remaining usages first
  • +
  • jet_brains_move (beta):
    Moves a symbol, file or directory to a new location, updating all references
  • +
  • jet_brains_find_declaration:
    Finds the declaration of a symbol using the JetBrains backend
  • +
  • jet_brains_find_implementations:
    Finds the implementations of a symbol (e.g. abstract method)
  • +
  • jet_brains_inline_symbol (beta):
    Inlines a symbol, replacing all call sites with the symbol’s body
  • +
+

+ We are far from done. Many new features will be coming your way – for both the LSP and JetBrains backends. +

+

+ Thank you to our contributors, users and customers! +

+
\ No newline at end of file From 0f6d46afeef2d81500175f506352f020f644f882 Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Fri, 3 Apr 2026 16:06:58 +0200 Subject: [PATCH 25/25] Prompt, minor --- src/serena/resources/config/prompt_templates/system_prompt.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/serena/resources/config/prompt_templates/system_prompt.yml b/src/serena/resources/config/prompt_templates/system_prompt.yml index 125a6818..de648821 100644 --- a/src/serena/resources/config/prompt_templates/system_prompt.yml +++ b/src/serena/resources/config/prompt_templates/system_prompt.yml @@ -3,7 +3,6 @@ # is encouraged (via the tool description) to call at the beginning of the conversation. prompts: system_prompt: | - You are a professional coding agent. You have access to semantic coding tools upon which you rely heavily for all your work. You operate in a resource-efficient and intelligent manner, always keeping in mind to not read or generate content that is not needed for the task at hand.