mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-14 18:19:14 +00:00
Minor cleanup after serena
This commit is contained in:
1 parent
468beb8fb1
commit
efb0690cf9
4 files changed
+31
-131
No files matched your search
@@ -1,46 +0,0 @@
|
||||
# Read-Only Mode
|
||||
|
||||
Serena supports a read-only mode that prevents any modifications to the codebase while still allowing analysis and exploration. This feature is useful for:
|
||||
|
||||
- Reviewing and analyzing code without the risk of accidental modifications
|
||||
- Allowing junior developers to explore a codebase with AI assistance but without modification privileges
|
||||
- Generating documentation or explanations without changing the underlying code
|
||||
- Creating proof-of-concept designs without implementing them
|
||||
|
||||
## Enabling Read-Only Mode
|
||||
|
||||
To enable read-only mode, set the `read_only` flag to `true` in your project configuration file:
|
||||
|
||||
```yaml
|
||||
# whether the project is in read-only mode
|
||||
read_only: true
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
When read-only mode is enabled:
|
||||
|
||||
1. All tools that perform editing operations are automatically disabled
|
||||
2. Any attempt to use an editing tool will result in an error message
|
||||
3. All non-editing tools remain fully functional
|
||||
|
||||
The editing tools that get disabled include:
|
||||
- `create_text_file`: Creating or overwriting files
|
||||
- `replace_symbol_body`: Replacing code symbol definitions
|
||||
- `insert_after_symbol`: Inserting code after symbols
|
||||
- `insert_before_symbol`: Inserting code before symbols
|
||||
- `delete_lines`: Deleting lines from files
|
||||
- `replace_lines`: Replacing lines in files
|
||||
- `insert_at_line`: Inserting content at specific lines
|
||||
- `execute_shell_command`: Executing shell commands that could potentially modify files
|
||||
- `insert_at_line`: Inserting content at specific lines
|
||||
|
||||
## Implementation Details
|
||||
|
||||
Serena uses the `EditingTool` marker class to identify tools that can modify code. The base `Tool` class includes a `can_edit()` method that checks if a tool is a subclass of `EditingTool`.
|
||||
|
||||
When a project is configured in read-only mode:
|
||||
1. During project activation, all editing tools are automatically excluded from the active tools list
|
||||
2. An additional runtime check prevents any editing tools from being executed, even if they somehow remain in the active tools list
|
||||
|
||||
This ensures that no modifications to the codebase can occur while in read-only mode.
|
||||
+28
-31
@@ -31,7 +31,6 @@ from serena.text_utils import search_files
|
||||
from serena.util.class_decorators import singleton
|
||||
from serena.util.file_system import scan_directory
|
||||
from serena.util.inspection import iter_subclasses
|
||||
from serena.editingtool import EditingTool
|
||||
from serena.util.shell import execute_shell_command
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -117,7 +116,6 @@ class ProjectConfig(ToStringMixin):
|
||||
return os.path.join(self.project_root, self.SERENA_MANAGED_DIR)
|
||||
|
||||
|
||||
|
||||
@singleton
|
||||
class SerenaConfig:
|
||||
"""
|
||||
@@ -269,7 +267,6 @@ class SerenaAgent:
|
||||
# When project activation is not enabled, we only expose the active tools
|
||||
return list(self._active_tools.values())
|
||||
|
||||
|
||||
def activate_project(self, project_config: ProjectConfig) -> None:
|
||||
log.info(f"Activating {project_config}")
|
||||
self.project_config = project_config
|
||||
@@ -282,13 +279,13 @@ class SerenaAgent:
|
||||
log.info(f"Active tools after exclusions ({len(self._active_tools)}): {', '.join(self.get_active_tool_names())}")
|
||||
else:
|
||||
self._active_tools = dict(self._all_tools)
|
||||
|
||||
|
||||
# if read_only mode is enabled, exclude all editing tools
|
||||
if self.project_config.read_only:
|
||||
self._active_tools = {
|
||||
key: tool for key, tool in self._active_tools.items() if not key.can_edit()
|
||||
}
|
||||
log.info(f"Project is in read-only mode. Editing tools excluded. Active tools ({len(self._active_tools)}): {', '.join(self.get_active_tool_names())}")
|
||||
self._active_tools = {key: tool for key, tool in self._active_tools.items() if not key.can_edit()}
|
||||
log.info(
|
||||
f"Project is in read-only mode. Editing tools excluded. Active tools ({len(self._active_tools)}): {', '.join(self.get_active_tool_names())}"
|
||||
)
|
||||
|
||||
# start the language server
|
||||
self.reset_language_server()
|
||||
@@ -302,7 +299,6 @@ class SerenaAgent:
|
||||
if self._project_activation_callback is not None:
|
||||
self._project_activation_callback()
|
||||
|
||||
|
||||
def get_active_tool_names(self) -> list[str]:
|
||||
"""
|
||||
:return: the list of names of the active tools for the current project
|
||||
@@ -436,6 +432,16 @@ class Component(ABC):
|
||||
_DEFAULT_MAX_ANSWER_LENGTH = int(2e5)
|
||||
|
||||
|
||||
class ToolMarkerCanEdit:
|
||||
"""
|
||||
Marker class for all tools that can perform editing operations on files.
|
||||
"""
|
||||
|
||||
|
||||
class ToolMarkerDoesNotRequireActiveProject:
|
||||
pass
|
||||
|
||||
|
||||
class Tool(Component):
|
||||
# NOTE: each tool should implement the apply method, which is then used in
|
||||
# the central method of the Tool class `apply_ex`.
|
||||
@@ -461,15 +467,15 @@ class Tool(Component):
|
||||
if apply_fn is None:
|
||||
raise RuntimeError(f"apply not defined in {self}. Did you forget to implement it?")
|
||||
return apply_fn
|
||||
|
||||
|
||||
@classmethod
|
||||
def can_edit(cls) -> bool:
|
||||
"""
|
||||
Returns whether this tool can perform editing operations on code.
|
||||
|
||||
|
||||
:return: True if the tool can edit code, False otherwise
|
||||
"""
|
||||
return issubclass(cls, EditingTool)
|
||||
return issubclass(cls, ToolMarkerCanEdit)
|
||||
|
||||
@classmethod
|
||||
def get_tool_description(cls) -> str:
|
||||
@@ -533,13 +539,9 @@ class Tool(Component):
|
||||
f"Error: Tool '{self.get_name()}' is disabled for the active project ('{self.project_config.project_name}'); "
|
||||
f"active tools: {self.agent.get_active_tool_names()}"
|
||||
)
|
||||
|
||||
|
||||
# check if the project is in read-only mode and this is an editing tool
|
||||
if (
|
||||
self.agent.project_config is not None
|
||||
and self.agent.project_config.read_only
|
||||
and self.__class__.can_edit()
|
||||
):
|
||||
if self.agent.project_config is not None and self.agent.project_config.read_only and self.__class__.can_edit():
|
||||
return (
|
||||
f"Error: Tool '{self.get_name()}' cannot be used because the project '{self.project_config.project_name}' "
|
||||
f"is in read-only mode. Editing operations are not allowed."
|
||||
@@ -561,11 +563,6 @@ class Tool(Component):
|
||||
return result
|
||||
|
||||
|
||||
|
||||
class ToolMarkerDoesNotRequireActiveProject:
|
||||
pass
|
||||
|
||||
|
||||
class RestartLanguageServerTool(Tool):
|
||||
"""Restarts the language server, may be necessary when edits not through Serena happen."""
|
||||
|
||||
@@ -613,7 +610,7 @@ class ReadFileTool(Tool):
|
||||
return self._limit_length(result, max_answer_chars)
|
||||
|
||||
|
||||
class CreateTextFileTool(Tool, EditingTool):
|
||||
class CreateTextFileTool(Tool, ToolMarkerCanEdit):
|
||||
"""
|
||||
Creates/overwrites a file in the project directory.
|
||||
"""
|
||||
@@ -865,7 +862,7 @@ class FindReferencingCodeSnippetsTool(Tool):
|
||||
return self._limit_length(result_json_str, max_answer_chars)
|
||||
|
||||
|
||||
class ReplaceSymbolBodyTool(Tool, EditingTool):
|
||||
class ReplaceSymbolBodyTool(Tool, ToolMarkerCanEdit):
|
||||
"""
|
||||
Replaces the full definition of a symbol.
|
||||
"""
|
||||
@@ -894,7 +891,7 @@ class ReplaceSymbolBodyTool(Tool, EditingTool):
|
||||
return SUCCESS_RESULT
|
||||
|
||||
|
||||
class InsertAfterSymbolTool(Tool, EditingTool):
|
||||
class InsertAfterSymbolTool(Tool, ToolMarkerCanEdit):
|
||||
"""
|
||||
Inserts content after the end of the definition of a given symbol.
|
||||
"""
|
||||
@@ -923,7 +920,7 @@ class InsertAfterSymbolTool(Tool, EditingTool):
|
||||
return SUCCESS_RESULT
|
||||
|
||||
|
||||
class InsertBeforeSymbolTool(Tool, EditingTool):
|
||||
class InsertBeforeSymbolTool(Tool, ToolMarkerCanEdit):
|
||||
"""
|
||||
Inserts content before the beginning of the definition of a given symbol.
|
||||
"""
|
||||
@@ -952,7 +949,7 @@ class InsertBeforeSymbolTool(Tool, EditingTool):
|
||||
return SUCCESS_RESULT
|
||||
|
||||
|
||||
class DeleteLinesTool(Tool, EditingTool):
|
||||
class DeleteLinesTool(Tool, ToolMarkerCanEdit):
|
||||
"""
|
||||
Deletes a range of lines within a file.
|
||||
"""
|
||||
@@ -979,7 +976,7 @@ class DeleteLinesTool(Tool, EditingTool):
|
||||
return SUCCESS_RESULT
|
||||
|
||||
|
||||
class ReplaceLinesTool(Tool, EditingTool):
|
||||
class ReplaceLinesTool(Tool, ToolMarkerCanEdit):
|
||||
"""
|
||||
Replaces a range of lines within a file with new content.
|
||||
"""
|
||||
@@ -1010,7 +1007,7 @@ class ReplaceLinesTool(Tool, EditingTool):
|
||||
return SUCCESS_RESULT
|
||||
|
||||
|
||||
class InsertAtLineTool(Tool, EditingTool):
|
||||
class InsertAtLineTool(Tool, ToolMarkerCanEdit):
|
||||
"""
|
||||
Inserts content at a given line in a file.
|
||||
"""
|
||||
@@ -1282,7 +1279,7 @@ class SearchForPatternTool(Tool):
|
||||
return self._limit_length(result, max_answer_chars)
|
||||
|
||||
|
||||
class ExecuteShellCommandTool(Tool, EditingTool):
|
||||
class ExecuteShellCommandTool(Tool, ToolMarkerCanEdit):
|
||||
"""
|
||||
Executes a shell command.
|
||||
"""
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
from typing import Type
|
||||
|
||||
|
||||
class EditingTool:
|
||||
"""
|
||||
A marker class for all tools that perform editing operations on code.
|
||||
This class is used to identify tools that modify code in the project.
|
||||
Tools that inherit from this class will be excluded when the project
|
||||
is in read-only mode.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def can_edit(cls) -> bool:
|
||||
"""
|
||||
Returns True as editing tools are designed to modify code.
|
||||
This method can be overridden by subclasses if needed.
|
||||
|
||||
:return: True if the tool can edit code, False otherwise
|
||||
"""
|
||||
return True
|
||||
@@ -1,44 +1,13 @@
|
||||
import pytest
|
||||
from serena.agent import Tool, CreateTextFileTool, ReadFileTool, ProjectConfig
|
||||
from serena.editingtool import EditingTool
|
||||
from serena.agent import CreateTextFileTool, ReadFileTool, Tool
|
||||
|
||||
|
||||
class TestEditMarker:
|
||||
def test_edit_marker_class(self):
|
||||
"""Test that EditingTool marker class exists"""
|
||||
assert EditingTool.__doc__ is not None
|
||||
assert hasattr(EditingTool, 'can_edit')
|
||||
assert EditingTool.can_edit() is True
|
||||
|
||||
def test_tool_can_edit_method(self):
|
||||
"""Test that Tool.can_edit() method works correctly"""
|
||||
# Non-editing tool should return False
|
||||
assert issubclass(ReadFileTool, Tool)
|
||||
assert not issubclass(ReadFileTool, EditingTool)
|
||||
assert not ReadFileTool.can_edit()
|
||||
|
||||
|
||||
# Editing tool should return True
|
||||
assert issubclass(CreateTextFileTool, Tool)
|
||||
assert issubclass(CreateTextFileTool, EditingTool)
|
||||
assert CreateTextFileTool.can_edit()
|
||||
|
||||
def test_project_config_read_only(self):
|
||||
"""Test that ProjectConfig has read_only property"""
|
||||
config = ProjectConfig({
|
||||
"language": "python",
|
||||
"project_root": "/tmp",
|
||||
"ignore_all_files_in_gitignore": True,
|
||||
"read_only": True
|
||||
}, "test_project")
|
||||
|
||||
assert hasattr(config, 'read_only')
|
||||
assert config.read_only is True
|
||||
|
||||
config = ProjectConfig({
|
||||
"language": "python",
|
||||
"project_root": "/tmp",
|
||||
"ignore_all_files_in_gitignore": True,
|
||||
# read_only not specified
|
||||
}, "test_project")
|
||||
|
||||
assert hasattr(config, 'read_only')
|
||||
assert config.read_only is False # Default should be False
|
||||
Reference in new issue
Block a user