From 3912a07fcbfa24794c684d144eacd9877b6ec5fb Mon Sep 17 00:00:00 2001
From: Michael Panchenko
Date: Wed, 25 Mar 2026 17:01:23 +0100
Subject: [PATCH 001/190] 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 002/190] 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 003/190] 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 004/190] 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 005/190] 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 006/190] 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 007/190] 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 008/190] 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 3929edede56627164bfdf2269ef65d1f2df3ec24 Mon Sep 17 00:00:00 2001
From: Dominik Jain
Date: Thu, 2 Apr 2026 10:31:48 +0200
Subject: [PATCH 009/190] _iter_gitignore_files: Catch PermissionError
Relates to #1268
---
src/serena/util/file_system.py | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/src/serena/util/file_system.py b/src/serena/util/file_system.py
index 3784a17a..d7d9f956 100644
--- a/src/serena/util/file_system.py
+++ b/src/serena/util/file_system.py
@@ -165,10 +165,14 @@ class GitignoreParser:
def scan(abs_path: str | None) -> Iterator[str]:
for entry in os.scandir(abs_path):
- if entry.is_dir(follow_symlinks=follow_symlinks):
- queue.append(entry.path)
- elif entry.is_file(follow_symlinks=follow_symlinks) and entry.name == ".gitignore":
- yield entry.path
+ try:
+ if entry.is_dir(follow_symlinks=follow_symlinks):
+ queue.append(entry.path)
+ elif entry.is_file(follow_symlinks=follow_symlinks) and entry.name == ".gitignore":
+ yield entry.path
+ except PermissionError as ex:
+ log.debug(f"Skipping entry due to permission error: {entry.path}", exc_info=ex)
+ continue
while queue:
next_abs_path = queue.pop(0)
From a45fe0cf1096c8a0f05f17c4752477e02a3f1f76 Mon Sep 17 00:00:00 2001
From: Michael Panchenko
Date: Thu, 2 Apr 2026 10:37:28 +0200
Subject: [PATCH 010/190] 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 33b4d08c9b7a55e13cdcfb6834679f638045a641 Mon Sep 17 00:00:00 2001
From: Michael Panchenko
Date: Thu, 2 Apr 2026 10:58:37 +0200
Subject: [PATCH 011/190] Updated usage docs for codex
---
docs/02-usage/030_clients.md | 23 ++++++++---------------
1 file changed, 8 insertions(+), 15 deletions(-)
diff --git a/docs/02-usage/030_clients.md b/docs/02-usage/030_clients.md
index 8db7e6e8..98b615c2 100644
--- a/docs/02-usage/030_clients.md
+++ b/docs/02-usage/030_clients.md
@@ -153,25 +153,18 @@ For example, when using `uvx`, add the following section:
```toml
[mcp_servers.serena]
+startup_timeout_sec = 25
command = "uvx"
-args = ["--from", "git+https://github.com/oraios/serena", "serena", "start-mcp-server", "--context", "codex"]
+args = ["--from", "git+https://github.com/oraios/serena", "serena", "start-mcp-server", "--project-from-cwd", "--context", "codex"]
```
-After codex has started, you need to activate the project, which you can do by saying:
+The larger startup timeout is to permit uvx to download the necessary dependencies. Once downloaded, the startup time is much faster.
-> Call serena.activate_project, serena.check_onboarding_performed and serena.initial_instructions
-
-**If you don't activate the project, you will not be able to use Serena's tools!**
-
-It is recommend to set this prompt as a [custom prompt](https://developers.openai.com/codex/custom-prompts), so you don't need to type this every time.
-
-That's it! Have a look at `~/.codex/log/codex-tui.log` to see if any errors occurred.
-
-Serena's dashboard will run if you have not disabled it in the configuration, but due to Codex's sandboxing, the web browser
-may not open automatically. You can open it manually by going to `http://localhost:24282/dashboard/index.html` (or a higher port, if
-that was already taken).
-
-> Codex will often show the tools as `failed` even though they are successfully executed. This is not a problem, seems to be a bug in Codex. Despite the error message, everything works as expected.
+:::{attention}
+Codex currently ignores the [instructions](https://blog.modelcontextprotocol.io/posts/2025-11-03-using-server-instructions/)
+property of MCP servers, so it is recommended to prompt the agent to
+"read initial instructions for Serena" when starting your session, e.g. by mentioning this in your `AGENT.md`.
+:::
## Claude Desktop
From d52d24efe2061c554c570988f561f338ad3d709d Mon Sep 17 00:00:00 2001
From: Michael Panchenko
Date: Thu, 2 Apr 2026 11:25:52 +0200
Subject: [PATCH 012/190] Docs: added `-p 3.13` to configuration examples
---
docs/02-usage/030_clients.md | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/docs/02-usage/030_clients.md b/docs/02-usage/030_clients.md
index 98b615c2..bcb91e57 100644
--- a/docs/02-usage/030_clients.md
+++ b/docs/02-usage/030_clients.md
@@ -128,6 +128,8 @@ While serena can be directly installed from the GitHub MCP server registry, we r
"type": "stdio",
"command": "uvx",
"args": [
+ "-p",
+ "3.13",
"--from",
"git+https://github.com/oraios/serena",
"serena",
@@ -155,7 +157,7 @@ For example, when using `uvx`, add the following section:
[mcp_servers.serena]
startup_timeout_sec = 25
command = "uvx"
-args = ["--from", "git+https://github.com/oraios/serena", "serena", "start-mcp-server", "--project-from-cwd", "--context", "codex"]
+args = ["-p", "3.13", "--from", "git+https://github.com/oraios/serena", "serena", "start-mcp-server", "--project-from-cwd", "--context", "codex"]
```
The larger startup timeout is to permit uvx to download the necessary dependencies. Once downloaded, the startup time is much faster.
@@ -182,6 +184,8 @@ Add the `serena` MCP server configuration
"serena": {
"command": "uvx",
"args": [
+ "-p",
+ "3.13",
"--from",
"git+https://github.com/oraios/serena",
"serena",
@@ -218,6 +222,8 @@ MCP server configuration:
"serena": {
"command": "uvx",
"args": [
+ "-p",
+ "3.13",
"--from",
"git+https://github.com/oraios/serena",
"serena",
@@ -246,6 +252,8 @@ Go to Settings / Tools / AI Assistant / MCP and add a new **local** configuratio
"serena": {
"command": "uvx",
"args": [
+ "-p",
+ "3.13",
"--from",
"git+https://github.com/oraios/serena",
"serena",
@@ -272,6 +280,8 @@ Add this configuration:
"serena": {
"command": "uvx",
"args": [
+ "-p",
+ "3.13",
"--from",
"git+https://github.com/oraios/serena",
"serena",
From 6faf12b9fa7d0ef158aa7c2f00d3432ba6864c31 Mon Sep 17 00:00:00 2001
From: Dominik Jain
Date: Wed, 1 Apr 2026 15:17:47 +0200
Subject: [PATCH 013/190] Update uvx run instructions to include Python version
(3.13)
uvx ignores not only the lock file but also the Python
version specified in pyproject.toml.
https://github.com/astral-sh/uv/issues/8206#issuecomment-4113536371
---
README.md | 2 +-
docs/02-usage/020_running.md | 6 +++---
docs/02-usage/030_clients.md | 4 ++--
docs/02-usage/040_workflow.md | 4 ++--
docs/03-special-guides/serena_on_chatgpt.md | 4 ++--
5 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index d4fa11ed..2fadd208 100644
--- a/README.md
+++ b/README.md
@@ -106,7 +106,7 @@ See our [documentation page](https://oraios.github.io/serena/02-usage/025_jetbra
Issue this command to see available options:
```bash
-uvx --from git+https://github.com/oraios/serena serena start-mcp-server --help
+uvx -p 3.13 --from git+https://github.com/oraios/serena serena start-mcp-server --help
```
**Configuring Your Client**. To connect Serena to your preferred MCP client, you typically need to [configure a launch command in your client](https://oraios.github.io/serena/02-usage/030_clients.html).
diff --git a/docs/02-usage/020_running.md b/docs/02-usage/020_running.md
index 5766a987..4e2ca372 100644
--- a/docs/02-usage/020_running.md
+++ b/docs/02-usage/020_running.md
@@ -21,7 +21,7 @@ In general, to get help, append `--help` to the command, i.e.
`uvx` is part of `uv`. It can be used to run the latest version of Serena directly from the repository, without an explicit local installation.
- uvx --from git+https://github.com/oraios/serena serena
+ uvx -p 3.13 --from git+https://github.com/oraios/serena serena
Explore the CLI to see some of the customization options that serena provides (more info on them below).
@@ -124,7 +124,7 @@ case, you can simply run the `start-mcp-server` command without any additional o
For example, to run the server in stdio mode via `uvx`, you would run:
- uvx --from git+https://github.com/oraios/serena serena start-mcp-server
+ uvx -p 3.13 --from git+https://github.com/oraios/serena serena start-mcp-server
See the section ["Configuring Your MCP Client"](030_clients) for specific information on how to configure your MCP client (e.g. Claude Code, Codex, Cursor, etc.)
to use such a launch command.
@@ -143,7 +143,7 @@ via the `--port` option.
For example, to run the Serena MCP server in streamable HTTP mode on port 9121 using uvx,
you would run
- uvx --from git+https://github.com/oraios/serena serena start-mcp-server --transport streamable-http --port 9121
+ uvx -p 3.13 --from git+https://github.com/oraios/serena serena start-mcp-server --transport streamable-http --port 9121
and then configure your client to connect to `http://localhost:9121/mcp`.
diff --git a/docs/02-usage/030_clients.md b/docs/02-usage/030_clients.md
index bcb91e57..e0c13e73 100644
--- a/docs/02-usage/030_clients.md
+++ b/docs/02-usage/030_clients.md
@@ -79,7 +79,7 @@ Serena is a great way to make Claude Code both cheaper and more powerful!
use this command:
```shell
-claude mcp add serena -- uvx --from git+https://github.com/oraios/serena serena start-mcp-server --context claude-code --project "$(pwd)"
+claude mcp add serena -- uvx -p 3.13 --from git+https://github.com/oraios/serena serena start-mcp-server --context claude-code --project "$(pwd)"
```
Note:
@@ -92,7 +92,7 @@ Note:
**Global Configuration**. Alternatively, use `--project-from-cwd` for user-level configuration that works across all projects:
```shell
-claude mcp add --scope user serena -- uvx --from git+https://github.com/oraios/serena serena start-mcp-server --context=claude-code --project-from-cwd
+claude mcp add --scope user serena -- uvx -p 3.13 --from git+https://github.com/oraios/serena serena start-mcp-server --context=claude-code --project-from-cwd
```
Whenever you start Claude Code, Serena will search up from the current directory for `.serena/project.yml` or `.git` markers,
diff --git a/docs/02-usage/040_workflow.md b/docs/02-usage/040_workflow.md
index f8b3603f..380009c5 100644
--- a/docs/02-usage/040_workflow.md
+++ b/docs/02-usage/040_workflow.md
@@ -29,7 +29,7 @@ To explicitly create a project, use the following command while in the project d
For instance, when using `uvx`, run
- uvx --from git+https://github.com/oraios/serena serena project create [options]
+ uvx -p 3.13 --from git+https://github.com/oraios/serena serena project create [options]
* For an empty project, you will need to specify the programming language
(e.g., `--language python`).
@@ -201,7 +201,7 @@ Depending on the language backend being used, the management of resources for th
where `` is your way of running Serena. For example, when using `uvx`, run
- uvx --from git+https://github.com/oraios/serena serena start-project-server
+ uvx -p 3.13 --from git+https://github.com/oraios/serena serena start-project-server
### Multiple Agents Accessing a Single Serena Instance
diff --git a/docs/03-special-guides/serena_on_chatgpt.md b/docs/03-special-guides/serena_on_chatgpt.md
index ec872696..d5b640ba 100644
--- a/docs/03-special-guides/serena_on_chatgpt.md
+++ b/docs/03-special-guides/serena_on_chatgpt.md
@@ -17,7 +17,7 @@ Run the following command to launch Serena as http server (assuming port 8000):
```bash
uvx mcpo --port 8000 --api-key -- \
- uvx --from git+https://github.com/oraios/serena \
+ uvx -p 3.13 --from git+https://github.com/oraios/serena \
serena start-mcp-server --context chatgpt --project $(pwd)
```
@@ -28,7 +28,7 @@ You can also use other options, and you don't have to pass `--project` if you wa
or want to activate it later. See
```shell
-uvx --from git+https://github.com/oraios/serena serena start-mcp-server --help
+uvx -p 3.13 --from git+https://github.com/oraios/serena serena start-mcp-server --help
```
---
From 9f63606d7b80a635cc38f44ae882d50461cf0c7e Mon Sep 17 00:00:00 2001
From: Michael Panchenko
Date: Thu, 26 Mar 2026 10:45:10 +0100
Subject: [PATCH 014/190] 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.
-
-
-
-
+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!
-
-
-
-
-
-### 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 015/190] 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 016/190] 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 017/190] 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 018/190] 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 019/190] 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 020/190] 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 021/190] 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 c7df1167ae4515fb6eb93b084c2370cae1ceb8b3 Mon Sep 17 00:00:00 2001
From: Michael Panchenko
Date: Fri, 3 Apr 2026 13:50:45 +0200
Subject: [PATCH 022/190] Fix: exclude project memories from initial
instructions if read_memory is disabled
---
src/serena/agent.py | 45 ++++++++++++++++++++++++++++++--
src/serena/project.py | 23 +---------------
src/serena/tools/config_tools.py | 4 +--
3 files changed, 46 insertions(+), 26 deletions(-)
diff --git a/src/serena/agent.py b/src/serena/agent.py
index 91431192..c5cec083 100644
--- a/src/serena/agent.py
+++ b/src/serena/agent.py
@@ -2,6 +2,7 @@
The Serena Model Context Protocol (MCP) Server
"""
+import json
import multiprocessing
import os
import platform
@@ -35,7 +36,16 @@ from serena.ls_manager import LanguageServerManager
from serena.project import MemoriesManager, Project
from serena.prompt_factory import SerenaPromptFactory
from serena.task_executor import TaskExecutor
-from serena.tools import ActivateProjectTool, GetCurrentConfigTool, OpenDashboardTool, ReplaceContentTool, Tool, ToolMarker, ToolRegistry
+from serena.tools import (
+ ActivateProjectTool,
+ GetCurrentConfigTool,
+ OpenDashboardTool,
+ ReadMemoryTool,
+ ReplaceContentTool,
+ Tool,
+ ToolMarker,
+ ToolRegistry,
+)
from serena.util.gui import system_has_usable_display
from serena.util.inspection import iter_subclasses
from serena.util.logging import MemoryLogHandler
@@ -81,6 +91,9 @@ class AvailableTools:
def contains_tool_name(self, tool_name: str) -> bool:
return tool_name in self._tool_name_set
+ def contains_tool_class(self, tool_class: type[Tool]) -> bool:
+ return self.contains_tool_name(tool_class.get_name_from_cls())
+
class ToolSet:
"""
@@ -648,11 +661,39 @@ class SerenaAgent:
# If a project is active at startup, append its activation message
if self._active_project is not None:
- system_prompt += "\n\n" + self._active_project.get_activation_message()
+ system_prompt += "\n\n" + self.get_project_activation_message()
log.info("System prompt:\n%s", system_prompt)
return system_prompt
+ def get_project_activation_message(self) -> str:
+ """
+ :return: a message providing information about the project upon activation (e.g. programming language, memories, initial prompt)
+ :raise: AssertionError if no project is active
+ """
+ proj = self._active_project
+ assert proj is not None, "A project must be active before calling this."
+ if proj.is_newly_created:
+ msg = f"Created and activated a new project with name '{proj.project_name}' at {proj.project_root}. "
+ else:
+ msg = f"The project with name '{proj.project_name}' at {proj.project_root} is activated."
+ if self._language_backend == LanguageBackend.LSP:
+ languages_str = ", ".join([lang.value for lang in proj.project_config.languages])
+ msg += f"\nProgramming languages: {languages_str}."
+ msg += "File encoding: {proj.project_config.encoding}."
+
+ include_memories = self._active_tools.contains_tool_class(ReadMemoryTool)
+ if include_memories:
+ project_memories = proj.memories_manager.list_project_memories()
+ if project_memories:
+ msg += (
+ f"\n{json.dumps(project_memories.to_dict())}\n"
+ + "Use the `read_memory` tool to read these memories later if they are relevant to the task."
+ )
+ if proj.project_config.initial_prompt:
+ msg += f"\nAdditional project-specific instructions:\n {proj.project_config.initial_prompt}"
+ return msg
+
def _update_active_modes(self) -> None:
"""
Updates the active modes based on the Serena configuration, the active project configuration (if any),
diff --git a/src/serena/project.py b/src/serena/project.py
index 5995c3e7..a20623e3 100644
--- a/src/serena/project.py
+++ b/src/serena/project.py
@@ -1,4 +1,3 @@
-import json
import logging
import os
import re
@@ -291,7 +290,7 @@ class Project(ToStringMixin):
self.language_server_manager: LanguageServerManager | None = None
self._language_server_manager_init_error: Exception | None = None
- self._is_newly_created = is_newly_created
+ self.is_newly_created = is_newly_created
self._agent: Optional["SerenaAgent"] = None
# create .gitignore file in the project's Serena data folder if not yet present
@@ -379,26 +378,6 @@ class Project(ToStringMixin):
def path_to_project_yml(self) -> str:
return self.serena_config.get_project_yml_location(self.project_root)
- def get_activation_message(self) -> str:
- """
- :return: a message providing information about the project upon activation (e.g. programming language, memories, initial prompt)
- """
- if self._is_newly_created:
- msg = f"Created and activated a new project with name '{self.project_name}' at {self.project_root}. "
- else:
- msg = f"The project with name '{self.project_name}' at {self.project_root} is activated."
- languages_str = ", ".join([lang.value for lang in self.project_config.languages])
- msg += f"\nProgramming languages: {languages_str}; file encoding: {self.project_config.encoding}"
- project_memories = self.memories_manager.list_project_memories()
- if project_memories:
- msg += (
- f"\nAvailable project memories: {json.dumps(project_memories.to_dict())}\n"
- + "Use the `read_memory` tool to read these memories later if they are relevant to the task."
- )
- if self.project_config.initial_prompt:
- msg += f"\nAdditional project-specific instructions:\n {self.project_config.initial_prompt}"
- return msg
-
def read_file(self, relative_path: str) -> str:
"""
Reads a file relative to the project root.
diff --git a/src/serena/tools/config_tools.py b/src/serena/tools/config_tools.py
index 7a0472cf..bdab4706 100644
--- a/src/serena/tools/config_tools.py
+++ b/src/serena/tools/config_tools.py
@@ -28,8 +28,8 @@ class ActivateProjectTool(Tool, ToolMarkerDoesNotRequireActiveProject):
:param project: the name of a registered project to activate or a path to a project directory
"""
- active_project = self.agent.activate_project_from_path_or_name(project)
- result = active_project.get_activation_message()
+ self.agent.activate_project_from_path_or_name(project)
+ result = self.agent.get_project_activation_message()
result += "\nIMPORTANT: If you have not yet read the 'Serena Instructions Manual', do it now before continuing!"
return result
From 87accc6dd7b2cd22b6bbaa2d187766951cd765d5 Mon Sep 17 00:00:00 2001
From: Dominik Jain
Date: Fri, 3 Apr 2026 13:54:34 +0200
Subject: [PATCH 023/190] Fix lifecycle of dashboard viewer not being tied to
main process
---
src/serena/agent.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/serena/agent.py b/src/serena/agent.py
index c5cec083..1f6c09ae 100644
--- a/src/serena/agent.py
+++ b/src/serena/agent.py
@@ -592,7 +592,7 @@ class SerenaAgent:
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
- start_new_session=True, # Detach from parent process
+ start_new_session=False,
)
def get_exposed_tool_instances(self) -> list["Tool"]:
From 1ad26d5af317abd568a0ae35d066aeccf4151b26 Mon Sep 17 00:00:00 2001
From: Michael Panchenko
Date: Fri, 3 Apr 2026 14:37:13 +0200
Subject: [PATCH 024/190] 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 025/190] 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 026/190] 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 027/190] 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 028/190] 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 029/190] 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 030/190] 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 031/190] 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.
From f65980bd8cb9d71d744f1fb06cae9018902bdf4e Mon Sep 17 00:00:00 2001
From: Dominik Jain
Date: Fri, 3 Apr 2026 16:16:58 +0200
Subject: [PATCH 032/190] Set version to 1.0.0
---
pyproject.toml | 2 +-
src/serena/__init__.py | 2 +-
uv.lock | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 3d693eb9..680d8599 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ requires = ["hatchling"]
[project]
name = "serena-agent"
-version = "0.1.4"
+version = "1.0.0"
description = ""
authors = [{ name = "Oraios AI", email = "info@oraios-ai.de" }]
readme = "README.md"
diff --git a/src/serena/__init__.py b/src/serena/__init__.py
index a2c1cf0a..3ab32039 100644
--- a/src/serena/__init__.py
+++ b/src/serena/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "0.1.4"
+__version__ = "1.0.0"
import logging
diff --git a/uv.lock b/uv.lock
index 4151f3ae..ec3f4bd7 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2906,7 +2906,7 @@ wheels = [
[[package]]
name = "serena-agent"
-version = "0.1.4"
+version = "1.0.0"
source = { editable = "." }
dependencies = [
{ name = "anthropic" },
From 3c77e362caddc956dc280038f4c3f9b8c6c569d9 Mon Sep 17 00:00:00 2001
From: Michael Panchenko
Date: Fri, 3 Apr 2026 16:57:19 +0200
Subject: [PATCH 033/190] JB client: added version check to new methods,
renamed move_symbol to move
---
src/serena/jetbrains/jetbrains_plugin_client.py | 7 ++++++-
src/serena/tools/jetbrains_tools.py | 2 +-
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/serena/jetbrains/jetbrains_plugin_client.py b/src/serena/jetbrains/jetbrains_plugin_client.py
index f3ed6dc9..810618ba 100644
--- a/src/serena/jetbrains/jetbrains_plugin_client.py
+++ b/src/serena/jetbrains/jetbrains_plugin_client.py
@@ -425,13 +425,14 @@ class JetBrainsPluginClient(ToStringMixin):
self._postprocess_symbol_collection_response(symbol_collection)
return symbol_collection
- def move_symbol(
+ def move(
self,
name_path: str | None,
relative_path: str | None,
target_parent_name_path: str | None,
target_relative_path: str | None,
) -> dict[str, Any]:
+ self._require_version_at_least(2023, 2, 14)
request_data = {
"namePath": name_path,
"relativePath": relative_path,
@@ -527,6 +528,7 @@ class JetBrainsPluginClient(ToStringMixin):
: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
"""
+ self._require_version_at_least(2023, 2, 14)
request_data = {
"namePath": name_path,
"relativePath": relative_path,
@@ -548,6 +550,7 @@ class JetBrainsPluginClient(ToStringMixin):
:param relative_path: the relative path to the file containing the method
:param keep_definition: if True, keep the original method definition after inlining
"""
+ self._require_version_at_least(2023, 2, 14)
request_data = {
"namePath": name_path,
"relativePath": relative_path,
@@ -599,6 +602,7 @@ class JetBrainsPluginClient(ToStringMixin):
:param include_body: whether to include the symbol body
:param include_quick_info: whether to include quick info about the symbol
"""
+ self._require_version_at_least(2023, 2, 14)
request_data = {
"relativePath": relative_path,
"line": line,
@@ -618,6 +622,7 @@ class JetBrainsPluginClient(ToStringMixin):
:param name_path: the name path of the symbol
:param include_quick_info: whether to include quick info about the symbol
"""
+ self._require_version_at_least(2023, 2, 14)
request_data = {
"relativePath": relative_path,
"namePath": name_path,
diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py
index 1c4ef63f..00ae3a51 100644
--- a/src/serena/tools/jetbrains_tools.py
+++ b/src/serena/tools/jetbrains_tools.py
@@ -146,7 +146,7 @@ class JetBrainsMoveTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional, ToolMa
: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(
+ response_dict = client.move(
name_path=name_path,
relative_path=relative_path,
target_parent_name_path=target_parent_name_path,
From 9f28a2d75f5d05d5279c73d2992f700db398f6d9 Mon Sep 17 00:00:00 2001
From: Dominik Jain
Date: Sat, 4 Apr 2026 10:14:52 +0200
Subject: [PATCH 034/190] Restore LLM interaction section, adding block diagram
---
README.md | 19 +++++++++++++++++++
resources/serena-block-diagram.afdesign | Bin 0 -> 47098 bytes
resources/serena-block-diagram.svg | 1 +
3 files changed, 20 insertions(+)
create mode 100644 resources/serena-block-diagram.afdesign
create mode 100644 resources/serena-block-diagram.svg
diff --git a/README.md b/README.md
index feba7f7b..84519fa1 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,25 @@ approaches that rely on low-level concepts like line numbers or primitive search
Practically, this means that your agent operates **faster, more efficiently and more reliably**, especially in larger and
more complex codebases.
+## How Serena Works
+
+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.
+
+Serena can extend the functionality of your existing AI client via the **model context protocol (MCP)**.
+Most modern AI chat clients directly support MCP, including
+* terminal-based clients like Claude Code, Codex, OpenCode, or Gemini-CLI,
+* IDEs and IDE assistant plugins for VSCode, Cursor and JetBrains IDEs,
+* desktop and local web clients like Claude Desktop or OpenWebUI
+
+
+
+To connect the Serena MCP server to your client, you typically
+ * provide the client with a launch command that starts the MCP server, or
+ * start the Serena MCP server yourself in HTTP mode and provide the client with the URL.
+
+See the [Quick Start](#quick-start) section below for information on how to get started.
+
## Features
Serena supports two code intelligence backends:
diff --git a/resources/serena-block-diagram.afdesign b/resources/serena-block-diagram.afdesign
new file mode 100644
index 0000000000000000000000000000000000000000..49b898a1f4f03e6e313e2023eebbbf4d5a1b18ed
GIT binary patch
literal 47098
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