mirror of
https://github.com/tiennm99/serena.git
synced 2026-09-03 12:22:04 +00:00
Merge remote-tracking branch 'origin/main' into feature/project-change-tool
Conflicts: CHANGELOG.md README.md src/serena/agent.py
This commit is contained in:
+15
-9
@@ -1,12 +1,20 @@
|
||||
# Latest
|
||||
|
||||
Changes prior to the next official version change will appear here.
|
||||
|
||||
# 2025-04-07
|
||||
|
||||
* Allow Serena to switch between projects (project activation)
|
||||
* Add central Serena configuration in `serena_config.yml`, which
|
||||
* contains the list of available projects
|
||||
* allows to configure whether project activation is enabled
|
||||
* now contains the GUI logging configuration (project configurations no longer do)
|
||||
* Add new tools `activate_project` and `get_active_project`
|
||||
* Providing a project configuration file in the launch parameters is now optional
|
||||
* Serena core:
|
||||
* New tool: FindReferencingCodeSnippets
|
||||
* Adjusted prompt in CreateTextFileTool to prevent writing partial content (see [here](https://www.reddit.com/r/ClaudeAI/comments/1jpavtm/comment/mloek1x/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button)).
|
||||
* FindSymbolTool: allow passing a file for restricting search, not just a directory (Gemini was too dumb to pass directories)
|
||||
* Allow Serena to switch between projects (project activation)
|
||||
* Add central Serena configuration in `serena_config.yml`, which
|
||||
* contains the list of available projects
|
||||
* allows to configure whether project activation is enabled
|
||||
* now contains the GUI logging configuration (project configurations no longer do)
|
||||
* Add new tools `activate_project` and `get_active_project`
|
||||
* Providing a project configuration file in the launch parameters is now optional
|
||||
* Logging:
|
||||
* Improve error reporting in case of initialization failure:
|
||||
open a new GUI log window showing the error or ensure that the existing log window remains visible for some time
|
||||
@@ -19,5 +27,3 @@
|
||||
# 2025-04-01
|
||||
|
||||
Initial public version
|
||||
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ want to use Serena.
|
||||
|
||||
That's it! Save the config and then restart Claude Desktop.
|
||||
|
||||
Note: on Windows and MacOS there are official Claude Desktop applications by Anthropic, for Linux there is an [open-source
|
||||
Note: on Windows and macOS there are official Claude Desktop applications by Anthropic, for Linux there is an [open-source
|
||||
community version](https://github.com/aaddrick/claude-desktop-debian).
|
||||
|
||||
⚠️ Be sure to fully quit the Claude Desktop application, as closing Claude will just minimize it to the system tray – at least on Windows.
|
||||
@@ -571,6 +571,7 @@ Here is the full list of Serena's tools with a short description (output of `uv
|
||||
* `delete_lines`: Deletes a range of lines within a file.
|
||||
* `delete_memory`: Deletes a memory from Serena's project-specific memory store.
|
||||
* `execute_shell_command`: Executes a shell command.
|
||||
* `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
|
||||
* `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
|
||||
* `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
|
||||
* `get_active_project`: Gets the name of the currently active project (if any) and lists existing projects
|
||||
@@ -592,4 +593,4 @@ Here is the full list of Serena's tools with a short description (output of `uv
|
||||
* `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
|
||||
* `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
|
||||
* `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
|
||||
* `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
|
||||
* `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
|
||||
|
||||
@@ -746,7 +746,7 @@ class LanguageServer:
|
||||
self._cache_has_changed = True
|
||||
return result
|
||||
|
||||
async def request_full_symbol_tree(self, start_dir_relative_path: str | None = None, include_body: bool = False) -> List[multilspy_types.UnifiedSymbolInformation]:
|
||||
async def request_full_symbol_tree(self, within_relative_path: str | None = None, include_body: bool = False) -> List[multilspy_types.UnifiedSymbolInformation]:
|
||||
"""
|
||||
Will go through all files in the project and build a tree of symbols. Note: this may be slow the first time it is called.
|
||||
|
||||
@@ -755,19 +755,16 @@ class LanguageServer:
|
||||
that are within the repository.
|
||||
Will ignore all directories that start with a dot (.) and __pycache__ directories.
|
||||
|
||||
Args:
|
||||
start_dir_relative_path: if passed, only the symbols within this directory will be considered.
|
||||
include_body: whether to include the body of the symbols in the result.
|
||||
:param within_relative_path: pass a relative path to only consider symbols within this path.
|
||||
If a file is passed, only the symbols within this file will be considered.
|
||||
If a directory is passed, all files within this directory will be considered.
|
||||
:param include_body: whether to include the body of the symbols in the result.
|
||||
|
||||
Returns:
|
||||
A list of root symbols representing the top-level packages/modules in the project.
|
||||
:return: A list of root symbols representing the top-level packages/modules in the project.
|
||||
"""
|
||||
if not self.server_started:
|
||||
self.logger.log(
|
||||
"request_full_symbol_tree called before Language Server started",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise MultilspyException("Language Server not started")
|
||||
if within_relative_path is not None and os.path.isfile(within_relative_path):
|
||||
_, root_nodes = await self.request_document_symbols(within_relative_path, include_body=include_body)
|
||||
return root_nodes
|
||||
|
||||
# Helper function to check if a path should be ignored
|
||||
def should_ignore_dir(path: str) -> bool:
|
||||
@@ -854,7 +851,7 @@ class LanguageServer:
|
||||
return result
|
||||
|
||||
# Start from the root or the specified directory
|
||||
start_path = start_dir_relative_path or "."
|
||||
start_path = within_relative_path or "."
|
||||
return await process_directory(start_path)
|
||||
|
||||
@staticmethod
|
||||
@@ -1589,7 +1586,7 @@ class SyncLanguageServer:
|
||||
).result()
|
||||
return result
|
||||
|
||||
def request_full_symbol_tree(self, start_package_relative_path: str | None = None, include_body: bool = False) -> List[multilspy_types.UnifiedSymbolInformation]:
|
||||
def request_full_symbol_tree(self, within_relative_path: str | None = None, include_body: bool = False) -> List[multilspy_types.UnifiedSymbolInformation]:
|
||||
"""
|
||||
Will go through all files in the project and build a tree of symbols. Note: this may be slow the first time it is called.
|
||||
|
||||
@@ -1598,15 +1595,16 @@ class SyncLanguageServer:
|
||||
that are within the repository.
|
||||
Will ignore all directories that start with a dot (.) and __pycache__ directories.
|
||||
|
||||
Args:
|
||||
start_package_relative_path: if passed, only the symbols within this directory will be considered.
|
||||
include_body: whether to include the body of the symbols in the result.
|
||||
:param within_relative_path: pass a relative path to only consider symbols within this path.
|
||||
If a file is passed, only the symbols within this file will be considered.
|
||||
If a directory is passed, all files within this directory will be considered.
|
||||
If None, the entire codebase will be considered.
|
||||
:param include_body: whether to include the body of the symbols in the result.
|
||||
|
||||
Returns:
|
||||
A list of root symbols representing the top-level packages/modules in the project.
|
||||
:return: A list of root symbols representing the top-level packages/modules in the project.
|
||||
"""
|
||||
result = asyncio.run_coroutine_threadsafe(
|
||||
self.language_server.request_full_symbol_tree(start_package_relative_path, include_body), self.loop
|
||||
self.language_server.request_full_symbol_tree(within_relative_path, include_body), self.loop
|
||||
).result()
|
||||
return result
|
||||
|
||||
|
||||
+55
-5
@@ -22,7 +22,7 @@ from multilspy import SyncLanguageServer
|
||||
from multilspy.multilspy_config import Language, MultilspyConfig
|
||||
from multilspy.multilspy_logger import MultilspyLogger
|
||||
from multilspy.multilspy_types import SymbolKind
|
||||
from serena import serena_root_path
|
||||
from serena import __version__, serena_root_path
|
||||
from serena.gui_log_viewer import GuiLogViewer, GuiLogViewerHandler
|
||||
from serena.llm.prompt_factory import PromptFactory
|
||||
from serena.symbol import SymbolLocation, SymbolManager
|
||||
@@ -145,7 +145,7 @@ class SerenaAgent:
|
||||
self._gui_log_handler = GuiLogViewerHandler(GuiLogViewer(title="Serena Logs"), level=log_level, format_string=LOG_FORMAT)
|
||||
Logger.root.addHandler(self._gui_log_handler)
|
||||
|
||||
log.info(f"Starting Serena server (process id={os.getpid()}, parent process id={os.getppid()})")
|
||||
log.info(f"Starting Serena server (version={__version__}, process id={os.getpid()}, parent process id={os.getppid()})")
|
||||
log.info("Available projects: {}".format(", ".join(self.serena_config.project_names)))
|
||||
|
||||
self.prompt_factory = PromptFactory()
|
||||
@@ -511,6 +511,10 @@ class CreateTextFileTool(Tool):
|
||||
You can also use insert_at_line to insert content at a specific line for existing files if the symbolic operations
|
||||
are not the right choice for what you want to do.
|
||||
|
||||
If ever used on an existing file, the content has to be the complete content of that file (so it
|
||||
may never end with something like "The remaining content of the file is left unchanged.").
|
||||
For operations that just replace a part of a file, use the replace_lines or the symbolic editing tools instead.
|
||||
|
||||
:param relative_path: the relative path to the file to create
|
||||
:param content: the (utf-8-encoded) content to write to the file
|
||||
:return: a message indicating success or failure
|
||||
@@ -604,11 +608,11 @@ class FindSymbolTool(Tool):
|
||||
self,
|
||||
name: str,
|
||||
depth: int = 0,
|
||||
within_relative_path: str | None = None,
|
||||
include_body: bool = False,
|
||||
include_kinds: list[int] | None = None,
|
||||
exclude_kinds: list[int] | None = None,
|
||||
substring_matching: bool = False,
|
||||
dir_relative_path: str | None = None,
|
||||
max_answer_chars: int = _DEFAULT_MAX_ANSWER_LENGTH,
|
||||
) -> str:
|
||||
"""
|
||||
@@ -624,7 +628,9 @@ class FindSymbolTool(Tool):
|
||||
(e.g. depth 1 will retrieve methods and attributes for the case where the symbol refers to a class).
|
||||
Provide a non-zero depth if you intend to subsequently query symbols that are contained in the
|
||||
retrieved symbol.
|
||||
:param dir_relative_path: pass a directory relative path to only consider symbols within this directory.
|
||||
:param within_relative_path: pass a relative path to only consider symbols within this path.
|
||||
If a file is passed, only the symbols within this file will be considered.
|
||||
If a directory is passed, all files within this directory will be considered.
|
||||
If None, the entire codebase will be considered.
|
||||
:param include_body: whether to include the body of all symbols in the result. You should only use this
|
||||
if you actually need the body of the symbol for the task at hand (for example, for a deep analysis
|
||||
@@ -653,7 +659,7 @@ class FindSymbolTool(Tool):
|
||||
include_kinds=include_kinds,
|
||||
exclude_kinds=exclude_kinds,
|
||||
substring_matching=substring_matching,
|
||||
dir_relative_path=dir_relative_path,
|
||||
within_relative_path=within_relative_path,
|
||||
)
|
||||
symbol_dicts = [s.to_dict(kind=True, location=True, depth=depth, include_body=include_body) for s in symbols]
|
||||
result = json.dumps(symbol_dicts)
|
||||
@@ -715,6 +721,50 @@ class FindReferencingSymbolsTool(Tool):
|
||||
return self._limit_length(result, max_answer_chars)
|
||||
|
||||
|
||||
class FindReferencingCodeSnippetsTool(Tool):
|
||||
"""
|
||||
Finds code snippets in which the symbol at the given location is referenced.
|
||||
"""
|
||||
|
||||
def apply(
|
||||
self,
|
||||
relative_path: str,
|
||||
line: int,
|
||||
column: int,
|
||||
context_lines_before: int = 0,
|
||||
context_lines_after: int = 0,
|
||||
max_answer_chars: int = _DEFAULT_MAX_ANSWER_LENGTH,
|
||||
) -> str:
|
||||
"""
|
||||
Returns short code snippets where the symbol at the given location is referenced.
|
||||
|
||||
Contrary to the `find_referencing_symbols` tool, this tool returns references that are not symbols but instead
|
||||
code snippets that may or may not be contained in a symbol (for example, file-level calls).
|
||||
It may make sense to use this tool to get a quick overview of the code that references
|
||||
the symbol. Usually, just looking at code snippets is not enough to understand the full context,
|
||||
unless the case you are investigating is very simple,
|
||||
or you already have read the relevant symbols using the find_referencing_symbols tool and
|
||||
now want to get an overview of how the referenced symbol (at the given location) is used in them.
|
||||
The size of the snippets is controlled by the context_lines_before and context_lines_after parameters.
|
||||
|
||||
:param relative_path: the relative path to the file containing the symbol
|
||||
:param line: the line number of the symbol to find references for
|
||||
:param column: the column of the symbol to find references for
|
||||
:param context_lines_before: the number of lines to include before the line containing the reference
|
||||
:param context_lines_after: the number of lines to include after the line containing the reference
|
||||
:param max_answer_chars: if the output is longer than this number of characters,
|
||||
no content will be returned. Don't adjust unless there is really no other way to get the content
|
||||
required for the task. Instead, if the output is too long, you should
|
||||
make a stricter query.
|
||||
"""
|
||||
matches = self.language_server.request_references_with_content(
|
||||
relative_path, line, column, context_lines_before, context_lines_after
|
||||
)
|
||||
result = [match.to_display_string() for match in matches]
|
||||
result_json_str = json.dumps(result)
|
||||
return self._limit_length(result_json_str, max_answer_chars)
|
||||
|
||||
|
||||
class ReplaceSymbolBodyTool(Tool):
|
||||
"""
|
||||
Replaces the full definition of a symbol.
|
||||
|
||||
@@ -200,7 +200,7 @@ class SymbolManager:
|
||||
def find_by_name(
|
||||
self,
|
||||
name: str,
|
||||
dir_relative_path: str | None = None,
|
||||
within_relative_path: str | None = None,
|
||||
include_body: bool = False,
|
||||
include_kinds: Sequence[SymbolKind] | None = None,
|
||||
exclude_kinds: Sequence[SymbolKind] | None = None,
|
||||
@@ -210,7 +210,9 @@ class SymbolManager:
|
||||
Find all symbols that match the given name.
|
||||
|
||||
:param name: the name of the symbol to find
|
||||
:param dir_relative_path: pass a directory relative path to only consider symbols within this directory.
|
||||
:param within_relative_path: pass a relative path to only consider symbols within this path.
|
||||
If a file is passed, only the symbols within this file will be considered.
|
||||
If a directory is passed, all files within this directory will be considered.
|
||||
If None, the entire codebase will be considered.
|
||||
:param include_body: whether to include the body of all symbols in the result.
|
||||
Note: you can filter out the bodies of the children if you set include_children_body=False
|
||||
@@ -224,7 +226,7 @@ class SymbolManager:
|
||||
:return: a list of symbols that match the given name
|
||||
"""
|
||||
symbols: list[Symbol] = []
|
||||
symbol_roots = self.lang_server.request_full_symbol_tree(start_package_relative_path=dir_relative_path, include_body=include_body)
|
||||
symbol_roots = self.lang_server.request_full_symbol_tree(within_relative_path=within_relative_path, include_body=include_body)
|
||||
for root in symbol_roots:
|
||||
symbols.extend(
|
||||
Symbol(root).find(name, include_kinds=include_kinds, exclude_kinds=exclude_kinds, substring_matching=substring_matching)
|
||||
|
||||
@@ -366,7 +366,7 @@ class TestLanguageServerSymbols:
|
||||
def test_symbol_tree_structure_subdir(self, language_server: SyncLanguageServer):
|
||||
"""Test that the symbol tree structure is correctly built."""
|
||||
# Get all symbols in the test file
|
||||
examples_package_roots = language_server.request_full_symbol_tree(start_package_relative_path="examples")
|
||||
examples_package_roots = language_server.request_full_symbol_tree(within_relative_path="examples")
|
||||
assert len(examples_package_roots) == 1
|
||||
examples_package = examples_package_roots[0]
|
||||
assert examples_package["name"] == "examples"
|
||||
|
||||
Reference in New Issue
Block a user