mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-18 16:14:16 +00:00
Merge branch 'dev-v1.2.0'
This commit is contained in:
commit
4e0043a9b8
13 files changed
+347
-111
No files matched your search
+7
-1
@@ -3,7 +3,6 @@
|
|||||||
Status of the `main` branch. Changes prior to the next official version change will appear here.
|
Status of the `main` branch. Changes prior to the next official version change will appear here.
|
||||||
|
|
||||||
* General:
|
* General:
|
||||||
- Support `serena --version` CLI command for displaying the current version #1347
|
|
||||||
- Fix: Check for ignored path ignored `.git` folder only at the top level, not in every subdirectory (`Project._is_ignored_relative_path`) #1350
|
- Fix: Check for ignored path ignored `.git` folder only at the top level, not in every subdirectory (`Project._is_ignored_relative_path`) #1350
|
||||||
- `GetSymbolsOverviewTool`: ignored paths were not respected in LSP variant (fix in `SolidLanguageServer`)
|
- `GetSymbolsOverviewTool`: ignored paths were not respected in LSP variant (fix in `SolidLanguageServer`)
|
||||||
- Fix: Duplicate comments in re-saved YAML configuration files #1285
|
- Fix: Duplicate comments in re-saved YAML configuration files #1285
|
||||||
@@ -31,6 +30,13 @@ Status of the `main` branch. Changes prior to the next official version change w
|
|||||||
The `initial_instructions` tool provides the full prompt on demand, keeping the initial context lean.
|
The `initial_instructions` tool provides the full prompt on demand, keeping the initial context lean.
|
||||||
- Add `serena_info` tool for on-demand retrieval of usage information
|
- Add `serena_info` tool for on-demand retrieval of usage information
|
||||||
|
|
||||||
|
* CLI:
|
||||||
|
- Support `serena --version` CLI command for displaying the current version #1347
|
||||||
|
- Extend `prompts` subcommand with `print-prompt-template` and `print-cc-system-prompt-override`, improve `list` subcommand
|
||||||
|
|
||||||
|
* Clients:
|
||||||
|
- Document workaround to make Claude Code use Serena's tools after recent degradations caused by changes in CC harness and Opus 4.7 release.
|
||||||
|
|
||||||
* JetBrains:
|
* JetBrains:
|
||||||
- Add `debug` tool: The agent can set breakpoints, inspect variables, evaluate expressions and control execution flow
|
- Add `debug` tool: The agent can set breakpoints, inspect variables, evaluate expressions and control execution flow
|
||||||
by directly interacting with the IDE's debugger, using a REPL-style interface for maximum flexibility.
|
by directly interacting with the IDE's debugger, using a REPL-style interface for maximum flexibility.
|
||||||
|
|||||||
@@ -101,8 +101,32 @@ You can do this in Tools / GitHub Copilot / Chat, where at the bottom you can cl
|
|||||||
|
|
||||||
## Claude Code
|
## Claude Code
|
||||||
|
|
||||||
Serena is a great way to make Claude Code both cheaper and more powerful!
|
Serena is a great way to make Claude Code both more efficient and more powerful!
|
||||||
To add Serena to Claude Code, you can simply run `serena setup claude-code`. Alternatively, follow the instructions below.
|
To set up the Serena MCP server for Claude Code, you can simply run this command:
|
||||||
|
|
||||||
|
serena setup claude-code
|
||||||
|
|
||||||
|
Find manual setup instructions as well as workarounds for Claude Code's recent regressions pertaining to (external) tool use below.
|
||||||
|
|
||||||
|
:::{attention}
|
||||||
|
Recent updates to Claude Code (CC) and to the Opus line of models resulted in drastically reduced
|
||||||
|
adherence to instructions pertaining to Serena's tools.
|
||||||
|
|
||||||
|
After extensive analysis, we identified part of the reason to be very long and detailed
|
||||||
|
tool descriptions for built-in tools and parts of the default system prompt.
|
||||||
|
The descriptions of CC's system tools take almost 16k tokens, cannot be adjusted by the user,
|
||||||
|
and introduce a very strong bias towards internal tools, making it almost impossible to convince Opus 4.7 to use Serena.
|
||||||
|
|
||||||
|
As a workaround, we crafted a system prompt that counteracts this bias.
|
||||||
|
When using Serena, we highly recommend that you start CC as
|
||||||
|
|
||||||
|
```shell
|
||||||
|
claude --system-prompt="$(serena prompts print-cc-system-prompt-override)"
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also consider adding the content of `serena cc-system-prompt-override` to your `CLAUDE.md` files,
|
||||||
|
but the effect be insufficient for counteracting Claude Code's bias towards internal tools.
|
||||||
|
:::
|
||||||
|
|
||||||
**Global Configuration**. To add the Serena MCP server for all your projects, use the user-level configuration of claude code and the `--project-from-cwd` flag:
|
**Global Configuration**. To add the Serena MCP server for all your projects, use the user-level configuration of claude code and the `--project-from-cwd` flag:
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<div class="news-item">
|
||||||
|
<h3>Serena v1.2.0</h3>
|
||||||
|
<p class="date">April 27, 2026</p>
|
||||||
|
<p>
|
||||||
|
<b>Interactive Debugging.</b>
|
||||||
|
With the <a target="_blank" href="https://oraios.github.io/serena/02-usage/025_jetbrains_plugin.html">Serena JetBrains plugin</a>,
|
||||||
|
your agent can set now set breakpoints, inspect variables, evaluate expressions and control execution flow
|
||||||
|
by directly interacting with the IDE's debugger.
|
||||||
|
Our REPL-based interface is highly flexible, allowing all of this to be provided via a single tool.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Counteracting Claude Code Regressions.</b>
|
||||||
|
We analysed the issues causing Claude Code to use external tools increasingly suboptimally and implemented
|
||||||
|
another workaround (in addition to the existing hooks): a system prompt override.
|
||||||
|
Read our updated <a target="_blank" href="https://oraios.github.io/serena/02-usage/030_clients.html#claude-code">Claude code usage instructions</a> for more information.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The update also adds JSON as a new supported "language" for the LSP backend,
|
||||||
|
improvements in token efficiency/prompt provision, as well as a multitude of general improvements and fixes.
|
||||||
|
See our <a target="_blank" href="https://github.com/oraios/serena/blob/main/CHANGELOG.md">change log</a> for details.<br>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Update Now.</b>
|
||||||
|
If you are using the uv tool installation of Serena, upgrade as follows:<br>
|
||||||
|
uv tool upgrade serena-agent --prerelease=allow
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
from serena.cli import top_level
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
top_level(["--help"])
|
||||||
@@ -30,6 +30,9 @@ class JinjaTemplate(ParameterizedTemplateInterface):
|
|||||||
parsed_content = self._template.environment.parse(self._template_string)
|
parsed_content = self._template.environment.parse(self._template_string)
|
||||||
self._parameters = sorted(jinja2.meta.find_undeclared_variables(parsed_content))
|
self._parameters = sorted(jinja2.meta.find_undeclared_variables(parsed_content))
|
||||||
|
|
||||||
|
def get_template_string(self) -> str:
|
||||||
|
return self._template_string
|
||||||
|
|
||||||
def render(self, **params: Any) -> str:
|
def render(self, **params: Any) -> str:
|
||||||
"""Renders the template with the given kwargs. You can find out which parameters are required by calling get_parameter_names()."""
|
"""Renders the template with the given kwargs. You can find out which parameters are required by calling get_parameter_names()."""
|
||||||
return self._template.render(**params)
|
return self._template.render(**params)
|
||||||
|
|||||||
@@ -12,13 +12,17 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class PromptTemplate(ToStringMixin, ParameterizedTemplateInterface):
|
class PromptTemplate(ToStringMixin, ParameterizedTemplateInterface):
|
||||||
def __init__(self, name: str, jinja_template_string: str) -> None:
|
def __init__(self, name: str, jinja_template_string: str, path: str) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.path = path
|
||||||
self._jinja_template = JinjaTemplate(jinja_template_string.strip())
|
self._jinja_template = JinjaTemplate(jinja_template_string.strip())
|
||||||
|
|
||||||
def _tostring_exclude_private(self) -> bool:
|
def _tostring_exclude_private(self) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def get_template_string(self) -> str:
|
||||||
|
return self._jinja_template.get_template_string()
|
||||||
|
|
||||||
def render(self, **params: Any) -> str:
|
def render(self, **params: Any) -> str:
|
||||||
return self._jinja_template.render(**params)
|
return self._jinja_template.render(**params)
|
||||||
|
|
||||||
@@ -249,6 +253,7 @@ class MultiLangPromptCollection:
|
|||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
template_str: str,
|
template_str: str,
|
||||||
|
path: str,
|
||||||
lang_code: str = DEFAULT_LANG_CODE,
|
lang_code: str = DEFAULT_LANG_CODE,
|
||||||
on_name_collision: Literal["skip", "overwrite", "raise"] = "raise",
|
on_name_collision: Literal["skip", "overwrite", "raise"] = "raise",
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -259,7 +264,7 @@ class MultiLangPromptCollection:
|
|||||||
:param on_name_collision: how to deal with name/lang_code collisions
|
:param on_name_collision: how to deal with name/lang_code collisions
|
||||||
"""
|
"""
|
||||||
allow_overwrite = False
|
allow_overwrite = False
|
||||||
prompt_template = PromptTemplate(name, template_str)
|
prompt_template = PromptTemplate(name, template_str, path=path)
|
||||||
mlpt = self._multi_lang_prompt_templates.get(name)
|
mlpt = self._multi_lang_prompt_templates.get(name)
|
||||||
if mlpt is None:
|
if mlpt is None:
|
||||||
mlpt = MultiLangPromptTemplate(name)
|
mlpt = MultiLangPromptTemplate(name)
|
||||||
@@ -327,7 +332,7 @@ class MultiLangPromptCollection:
|
|||||||
self._add_prompt_list(prompt_name, prompt_template_or_list, lang_code=lang_code, on_name_collision=on_name_collision)
|
self._add_prompt_list(prompt_name, prompt_template_or_list, lang_code=lang_code, on_name_collision=on_name_collision)
|
||||||
elif isinstance(prompt_template_or_list, str):
|
elif isinstance(prompt_template_or_list, str):
|
||||||
self._add_prompt_template(
|
self._add_prompt_template(
|
||||||
prompt_name, prompt_template_or_list, lang_code=lang_code, on_name_collision=on_name_collision
|
prompt_name, prompt_template_or_list, lang_code=lang_code, path=path, on_name_collision=on_name_collision
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from .multilang_prompt import DEFAULT_LANG_CODE, LanguageFallbackMode, MultiLangPromptCollection, PromptList
|
from .multilang_prompt import DEFAULT_LANG_CODE, LanguageFallbackMode, MultiLangPromptCollection, PromptList, PromptTemplate
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -23,9 +23,18 @@ class PromptFactoryBase:
|
|||||||
self.lang_code = lang_code
|
self.lang_code = lang_code
|
||||||
self._prompt_collection = MultiLangPromptCollection(prompts_dir, fallback_mode=fallback_mode)
|
self._prompt_collection = MultiLangPromptCollection(prompts_dir, fallback_mode=fallback_mode)
|
||||||
|
|
||||||
|
def get_prompt_names(self) -> list[str]:
|
||||||
|
return self._prompt_collection.get_prompt_template_names()
|
||||||
|
|
||||||
|
def get_prompt_template(self, prompt_name: str) -> PromptTemplate:
|
||||||
|
return self._prompt_collection.get_prompt_template(prompt_name, lang_code=self.lang_code)
|
||||||
|
|
||||||
|
def get_prompt_template_string(self, prompt_name: str) -> str:
|
||||||
|
return self.get_prompt_template(prompt_name).get_template_string()
|
||||||
|
|
||||||
def _render_prompt(self, prompt_name: str, params: dict[str, Any]) -> str:
|
def _render_prompt(self, prompt_name: str, params: dict[str, Any]) -> str:
|
||||||
del params["self"]
|
del params["self"]
|
||||||
return self._prompt_collection.render_prompt_template(prompt_name, params, lang_code=self.lang_code)
|
return self.get_prompt_template(prompt_name).render(**params)
|
||||||
|
|
||||||
def _get_prompt_list(self, prompt_name: str) -> PromptList:
|
def _get_prompt_list(self, prompt_name: str) -> PromptList:
|
||||||
return self._prompt_collection.get_prompt_list(prompt_name, self.lang_code)
|
return self._prompt_collection.get_prompt_list(prompt_name, self.lang_code)
|
||||||
@@ -41,14 +50,12 @@ def autogenerate_prompt_factory_module(prompts_dir: str, target_module_path: str
|
|||||||
:param prompts_dir: the directory containing the prompt templates and prompt lists
|
:param prompts_dir: the directory containing the prompt templates and prompt lists
|
||||||
:param target_module_path: the path to the target module file (.py). Important: The module will be overwritten!
|
:param target_module_path: the path to the target module file (.py). Important: The module will be overwritten!
|
||||||
"""
|
"""
|
||||||
generated_code = """
|
generated_code = """# ruff: noqa
|
||||||
# ruff: noqa
|
|
||||||
# black: skip
|
# black: skip
|
||||||
# mypy: ignore-errors
|
# mypy: ignore-errors
|
||||||
|
|
||||||
# NOTE: This module is auto-generated from interprompt.autogenerate_prompt_factory_module, do not edit manually!
|
# NOTE: This module is auto-generated from interprompt.autogenerate_prompt_factory_module, do not edit manually!
|
||||||
|
|
||||||
from interprompt.multilang_prompt import PromptList
|
|
||||||
from interprompt.prompt_factory import PromptFactoryBase
|
from interprompt.prompt_factory import PromptFactoryBase
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -68,6 +75,9 @@ class PromptFactory(PromptFactoryBase):
|
|||||||
else:
|
else:
|
||||||
method_params_str = ", *, " + ", ".join([f"{param}: Any" for param in template_parameters])
|
method_params_str = ", *, " + ", ".join([f"{param}: Any" for param in template_parameters])
|
||||||
generated_code += f"""
|
generated_code += f"""
|
||||||
|
def get_{template_name}_template_string(self) -> str:
|
||||||
|
return self.get_prompt_template_string('{template_name}')
|
||||||
|
|
||||||
def create_{template_name}(self{method_params_str}) -> str:
|
def create_{template_name}(self{method_params_str}) -> str:
|
||||||
return self._render_prompt('{template_name}', locals())
|
return self._render_prompt('{template_name}', locals())
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -869,18 +869,6 @@ class SerenaAgent:
|
|||||||
raise ValueError("No active project. Please activate a project first.")
|
raise ValueError("No active project. Please activate a project first.")
|
||||||
return project
|
return project
|
||||||
|
|
||||||
def set_modes(self, mode_names: list[str]) -> None:
|
|
||||||
"""
|
|
||||||
Set the current mode configurations.
|
|
||||||
|
|
||||||
:param mode_names: List of mode names or paths to use
|
|
||||||
"""
|
|
||||||
self._mode_overrides = ModeSelectionDefinition(default_modes=mode_names)
|
|
||||||
self._update_active_modes()
|
|
||||||
self._update_active_tools()
|
|
||||||
|
|
||||||
log.info(f"Set modes to {[mode.name for mode in self.get_active_modes()]}")
|
|
||||||
|
|
||||||
def get_active_modes(self) -> list[SerenaAgentMode]:
|
def get_active_modes(self) -> list[SerenaAgentMode]:
|
||||||
"""
|
"""
|
||||||
:return: the list of active modes
|
:return: the list of active modes
|
||||||
|
|||||||
+58
-8
@@ -18,7 +18,6 @@ from sensai.util.string import dict_string
|
|||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
from serena import serena_version
|
from serena import serena_version
|
||||||
from serena.agent import SerenaAgent
|
|
||||||
from serena.config.client_setup import client_setup_handlers
|
from serena.config.client_setup import client_setup_handlers
|
||||||
from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode
|
from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode
|
||||||
from serena.config.serena_config import (
|
from serena.config.serena_config import (
|
||||||
@@ -36,9 +35,7 @@ from serena.constants import (
|
|||||||
SERENAS_OWN_CONTEXT_YAMLS_DIR,
|
SERENAS_OWN_CONTEXT_YAMLS_DIR,
|
||||||
SERENAS_OWN_MODE_YAMLS_DIR,
|
SERENAS_OWN_MODE_YAMLS_DIR,
|
||||||
)
|
)
|
||||||
from serena.mcp import SerenaMCPFactory
|
from serena.prompt_factory import SerenaPromptFactory
|
||||||
from serena.project import Project
|
|
||||||
from serena.tools import FindReferencingSymbolsTool, FindSymbolTool, GetSymbolsOverviewTool, SearchForPatternTool, ToolRegistry
|
|
||||||
from serena.util.cli_util import AutoRegisteringGroup
|
from serena.util.cli_util import AutoRegisteringGroup
|
||||||
from serena.util.dataclass import get_dataclass_default
|
from serena.util.dataclass import get_dataclass_default
|
||||||
from serena.util.logging import MemoryLogHandler
|
from serena.util.logging import MemoryLogHandler
|
||||||
@@ -315,6 +312,8 @@ class TopLevelCommands(AutoRegisteringGroup):
|
|||||||
trace_lsp_communication: bool | None,
|
trace_lsp_communication: bool | None,
|
||||||
tool_timeout: float | None,
|
tool_timeout: float | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
from serena.mcp import SerenaMCPFactory
|
||||||
|
|
||||||
# initialize logging, using INFO level initially (will later be adjusted by SerenaAgent according to the config)
|
# initialize logging, using INFO level initially (will later be adjusted by SerenaAgent according to the config)
|
||||||
# * memory log handler (for use by GUI/Dashboard)
|
# * memory log handler (for use by GUI/Dashboard)
|
||||||
# * stream handler for stderr (for direct console output, which will also be captured by clients like Claude Desktop)
|
# * stream handler for stderr (for direct console output, which will also be captured by clients like Claude Desktop)
|
||||||
@@ -346,6 +345,7 @@ class TopLevelCommands(AutoRegisteringGroup):
|
|||||||
log.warning("No project root found from %s; not activating any project", os.getcwd())
|
log.warning("No project root found from %s; not activating any project", os.getcwd())
|
||||||
|
|
||||||
project_file = project_file_arg or project
|
project_file = project_file_arg or project
|
||||||
|
|
||||||
factory = SerenaMCPFactory(context=context, project=project_file, memory_log_handler=memory_log_handler)
|
factory = SerenaMCPFactory(context=context, project=project_file, memory_log_handler=memory_log_handler)
|
||||||
server = factory.create_mcp_server(
|
server = factory.create_mcp_server(
|
||||||
host=host,
|
host=host,
|
||||||
@@ -394,6 +394,8 @@ class TopLevelCommands(AutoRegisteringGroup):
|
|||||||
def print_system_prompt(
|
def print_system_prompt(
|
||||||
project: str, log_level: str, only_instructions: bool, context: str, modes: Sequence[str] | None = None
|
project: str, log_level: str, only_instructions: bool, context: str, modes: Sequence[str] | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
|
from serena.agent import SerenaAgent
|
||||||
|
|
||||||
prefix = "You will receive access to Serena's symbolic tools. Below are instructions for using them, take them into account."
|
prefix = "You will receive access to Serena's symbolic tools. Below are instructions for using them, take them into account."
|
||||||
postfix = "You begin by acknowledging that you understood the above instructions and are ready to receive tasks."
|
postfix = "You begin by acknowledging that you understood the above instructions and are ready to receive tasks."
|
||||||
|
|
||||||
@@ -403,9 +405,14 @@ class TopLevelCommands(AutoRegisteringGroup):
|
|||||||
modes_selection_def: ModeSelectionDefinition | None = None
|
modes_selection_def: ModeSelectionDefinition | None = None
|
||||||
if modes:
|
if modes:
|
||||||
modes_selection_def = ModeSelectionDefinition(default_modes=modes)
|
modes_selection_def = ModeSelectionDefinition(default_modes=modes)
|
||||||
|
serena_config = SerenaConfig.from_config_file()
|
||||||
|
serena_config.web_dashboard = False
|
||||||
|
print(serena_config.default_modes)
|
||||||
|
print(serena_config.base_modes)
|
||||||
|
|
||||||
agent = SerenaAgent(
|
agent = SerenaAgent(
|
||||||
project=os.path.abspath(project),
|
project=os.path.abspath(project),
|
||||||
serena_config=SerenaConfig(web_dashboard=False, log_level=lvl),
|
serena_config=serena_config,
|
||||||
context=context_instance,
|
context=context_instance,
|
||||||
modes=modes_selection_def,
|
modes=modes_selection_def,
|
||||||
)
|
)
|
||||||
@@ -828,6 +835,8 @@ class ProjectCommands(AutoRegisteringGroup):
|
|||||||
:param path: The path to check.
|
:param path: The path to check.
|
||||||
:param project: The path to the project directory, defaults to the current working directory.
|
:param project: The path to the project directory, defaults to the current working directory.
|
||||||
"""
|
"""
|
||||||
|
from serena.project import Project
|
||||||
|
|
||||||
serena_config = SerenaConfig.from_config_file()
|
serena_config = SerenaConfig.from_config_file()
|
||||||
proj = Project.load(os.path.abspath(project), serena_config=serena_config)
|
proj = Project.load(os.path.abspath(project), serena_config=serena_config)
|
||||||
if os.path.isabs(path):
|
if os.path.isabs(path):
|
||||||
@@ -851,6 +860,8 @@ class ProjectCommands(AutoRegisteringGroup):
|
|||||||
:param project: path to the project directory, defaults to the current working directory.
|
:param project: path to the project directory, defaults to the current working directory.
|
||||||
:param verbose: if set, prints detailed information about the indexed symbols.
|
:param verbose: if set, prints detailed information about the indexed symbols.
|
||||||
"""
|
"""
|
||||||
|
from serena.project import Project
|
||||||
|
|
||||||
serena_config = SerenaConfig.from_config_file()
|
serena_config = SerenaConfig.from_config_file()
|
||||||
proj = Project.load(os.path.abspath(project), serena_config=serena_config)
|
proj = Project.load(os.path.abspath(project), serena_config=serena_config)
|
||||||
if os.path.isabs(file):
|
if os.path.isabs(file):
|
||||||
@@ -887,6 +898,10 @@ class ProjectCommands(AutoRegisteringGroup):
|
|||||||
:param project: path to the project directory, defaults to the current working directory.
|
:param project: path to the project directory, defaults to the current working directory.
|
||||||
"""
|
"""
|
||||||
# NOTE: completely written by Claude Code, only functionality was reviewed, not implementation
|
# NOTE: completely written by Claude Code, only functionality was reviewed, not implementation
|
||||||
|
from serena.agent import SerenaAgent
|
||||||
|
from serena.project import Project
|
||||||
|
from serena.tools import FindReferencingSymbolsTool, FindSymbolTool, GetSymbolsOverviewTool, SearchForPatternTool
|
||||||
|
|
||||||
logging.configure(level=logging.INFO)
|
logging.configure(level=logging.INFO)
|
||||||
project_path = os.path.abspath(project)
|
project_path = os.path.abspath(project)
|
||||||
serena_config = SerenaConfig.from_config_file()
|
serena_config = SerenaConfig.from_config_file()
|
||||||
@@ -1040,6 +1055,8 @@ class ToolCommands(AutoRegisteringGroup):
|
|||||||
@click.option("--all", "-a", "include_optional", is_flag=True, help="List all tools, including those not enabled by default.")
|
@click.option("--all", "-a", "include_optional", is_flag=True, help="List all tools, including those not enabled by default.")
|
||||||
@click.option("--only-optional", is_flag=True, help="List only optional tools (those not enabled by default).")
|
@click.option("--only-optional", is_flag=True, help="List only optional tools (those not enabled by default).")
|
||||||
def list(quiet: bool = False, include_optional: bool = False, only_optional: bool = False) -> None:
|
def list(quiet: bool = False, include_optional: bool = False, only_optional: bool = False) -> None:
|
||||||
|
from serena.tools import ToolRegistry
|
||||||
|
|
||||||
tool_registry = ToolRegistry()
|
tool_registry = ToolRegistry()
|
||||||
if quiet:
|
if quiet:
|
||||||
if only_optional:
|
if only_optional:
|
||||||
@@ -1062,6 +1079,9 @@ class ToolCommands(AutoRegisteringGroup):
|
|||||||
@click.argument("tool_name", type=str)
|
@click.argument("tool_name", type=str)
|
||||||
@click.option("--context", type=str, default=None, help="Context name or path to context file.")
|
@click.option("--context", type=str, default=None, help="Context name or path to context file.")
|
||||||
def description(tool_name: str, context: str | None = None) -> None:
|
def description(tool_name: str, context: str | None = None) -> None:
|
||||||
|
from serena.agent import SerenaAgent
|
||||||
|
from serena.mcp import SerenaMCPFactory
|
||||||
|
|
||||||
# Load the context
|
# Load the context
|
||||||
serena_context = None
|
serena_context = None
|
||||||
if context:
|
if context:
|
||||||
@@ -1089,16 +1109,26 @@ class PromptCommands(AutoRegisteringGroup):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@click.command(
|
@click.command(
|
||||||
"list", help="Lists yamls that are used for defining prompts.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH}
|
"list", help="Lists prompt names and YAML files that can be overridden.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH}
|
||||||
)
|
)
|
||||||
def list() -> None:
|
def list() -> None:
|
||||||
|
# list prompt names
|
||||||
|
click.echo("Prompts:")
|
||||||
|
factory = SerenaPromptFactory()
|
||||||
|
for key in factory.get_prompt_names():
|
||||||
|
template = factory.get_prompt_template(key)
|
||||||
|
is_overridden = not template.path.startswith(PROMPT_TEMPLATES_DIR_INTERNAL)
|
||||||
|
click.echo(f" * '{key}' ({template.path if is_overridden else 'default'})")
|
||||||
|
|
||||||
|
# list prompts files
|
||||||
|
click.echo("\nPrompt files (which you can override with the create-override command):")
|
||||||
serena_prompt_yaml_names = [os.path.basename(f) for f in glob.glob(PROMPT_TEMPLATES_DIR_INTERNAL + "/*.yml")]
|
serena_prompt_yaml_names = [os.path.basename(f) for f in glob.glob(PROMPT_TEMPLATES_DIR_INTERNAL + "/*.yml")]
|
||||||
for prompt_yaml_name in serena_prompt_yaml_names:
|
for prompt_yaml_name in serena_prompt_yaml_names:
|
||||||
user_prompt_yaml_path = PromptCommands._get_user_prompt_yaml_path(prompt_yaml_name)
|
user_prompt_yaml_path = PromptCommands._get_user_prompt_yaml_path(prompt_yaml_name)
|
||||||
if os.path.exists(user_prompt_yaml_path):
|
if os.path.exists(user_prompt_yaml_path):
|
||||||
click.echo(f"{user_prompt_yaml_path} merged with default prompts in {prompt_yaml_name}")
|
click.echo(f" * {user_prompt_yaml_path} merged with default prompts in {prompt_yaml_name}")
|
||||||
else:
|
else:
|
||||||
click.echo(prompt_yaml_name)
|
click.echo(f" * {prompt_yaml_name}")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@click.command(
|
@click.command(
|
||||||
@@ -1171,6 +1201,26 @@ class PromptCommands(AutoRegisteringGroup):
|
|||||||
os.remove(user_prompt_yaml_path)
|
os.remove(user_prompt_yaml_path)
|
||||||
click.echo(f"Deleted override file '{prompt_yaml_name}'.")
|
click.echo(f"Deleted override file '{prompt_yaml_name}'.")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@click.command(
|
||||||
|
"print-prompt-template",
|
||||||
|
help="prints the (unrendered) template for the corresponding prompt name. "
|
||||||
|
"This respects custom prompt yaml overrides and thus will print the value that will be used in Serena",
|
||||||
|
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||||
|
)
|
||||||
|
@click.argument("prompt_name", type=str)
|
||||||
|
def print_prompt_template(prompt_name: str) -> None:
|
||||||
|
click.echo(SerenaPromptFactory().get_prompt_template_string(prompt_name))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@click.command(
|
||||||
|
"print-cc-system-prompt-override",
|
||||||
|
help="To be used specifically in Claude Code as value for `--system-prompt`",
|
||||||
|
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||||
|
)
|
||||||
|
def print_cc_system_prompt_override() -> None:
|
||||||
|
click.echo(SerenaPromptFactory().create_cc_system_prompt_override())
|
||||||
|
|
||||||
|
|
||||||
_mode = ModeCommands()
|
_mode = ModeCommands()
|
||||||
_context = ContextCommands()
|
_context = ContextCommands()
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
# NOTE: This module is auto-generated from interprompt.autogenerate_prompt_factory_module, do not edit manually!
|
# NOTE: This module is auto-generated from interprompt.autogenerate_prompt_factory_module, do not edit manually!
|
||||||
|
|
||||||
from interprompt.multilang_prompt import PromptList
|
|
||||||
from interprompt.prompt_factory import PromptFactoryBase
|
from interprompt.prompt_factory import PromptFactoryBase
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -14,15 +13,27 @@ class PromptFactory(PromptFactoryBase):
|
|||||||
A class for retrieving and rendering prompt templates and prompt lists.
|
A class for retrieving and rendering prompt templates and prompt lists.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def get_info_jet_brains_debug_repl_template_string(self) -> str:
|
||||||
|
return self.get_prompt_template_string("info_jet_brains_debug_repl")
|
||||||
|
|
||||||
def create_info_jet_brains_debug_repl(self) -> str:
|
def create_info_jet_brains_debug_repl(self) -> str:
|
||||||
return self._render_prompt("info_jet_brains_debug_repl", locals())
|
return self._render_prompt("info_jet_brains_debug_repl", locals())
|
||||||
|
|
||||||
|
def get_onboarding_prompt_template_string(self) -> str:
|
||||||
|
return self.get_prompt_template_string("onboarding_prompt")
|
||||||
|
|
||||||
def create_onboarding_prompt(self, *, system: Any) -> str:
|
def create_onboarding_prompt(self, *, system: Any) -> str:
|
||||||
return self._render_prompt("onboarding_prompt", locals())
|
return self._render_prompt("onboarding_prompt", locals())
|
||||||
|
|
||||||
|
def get_connection_prompt_template_string(self) -> str:
|
||||||
|
return self.get_prompt_template_string("connection_prompt")
|
||||||
|
|
||||||
def create_connection_prompt(self) -> str:
|
def create_connection_prompt(self) -> str:
|
||||||
return self._render_prompt("connection_prompt", locals())
|
return self._render_prompt("connection_prompt", locals())
|
||||||
|
|
||||||
|
def get_system_prompt_template_string(self) -> str:
|
||||||
|
return self.get_prompt_template_string("system_prompt")
|
||||||
|
|
||||||
def create_system_prompt(
|
def create_system_prompt(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -33,3 +44,9 @@ class PromptFactory(PromptFactoryBase):
|
|||||||
mode_system_prompts: Any,
|
mode_system_prompts: Any,
|
||||||
) -> str:
|
) -> str:
|
||||||
return self._render_prompt("system_prompt", locals())
|
return self._render_prompt("system_prompt", locals())
|
||||||
|
|
||||||
|
def get_cc_system_prompt_override_template_string(self) -> str:
|
||||||
|
return self.get_prompt_template_string("cc_system_prompt_override")
|
||||||
|
|
||||||
|
def create_cc_system_prompt_override(self) -> str:
|
||||||
|
return self._render_prompt("cc_system_prompt_override", locals())
|
||||||
@@ -3,14 +3,35 @@ prompt: |
|
|||||||
You are running in a CLI coding agent context where file operations, basic (line-based) edits and reads
|
You are running in a CLI coding agent context where file operations, basic (line-based) edits and reads
|
||||||
as well as shell commands are handled by your own, internal tools.
|
as well as shell commands are handled by your own, internal tools.
|
||||||
|
|
||||||
|
You have access to Serena's code intelligence tools that exploit the symbolic
|
||||||
|
structure of the code and are much more efficient than your own tools for most coding scenarios.
|
||||||
|
If you are working on any coding task and if Serena's tools are deferred, you
|
||||||
|
should load them all immediately, before performing any read, grep or bash commands.
|
||||||
If Serena's tools can be used to achieve your task, you should prioritize them.
|
If Serena's tools can be used to achieve your task, you should prioritize them.
|
||||||
**IMPORTANT**: If you haven't seen Serena's tools yet, you should use the tool search tool to load all of them
|
**CRITICAL**: If you haven't seen Serena's tools yet, you should use the tool search tool to load all of them
|
||||||
right now.
|
**right now**.
|
||||||
For exploring and reading code in a token-efficient manner, use Serena's code-centric overview and symbolic search tools.
|
For exploring and reading code in a token-efficient manner, use Serena's code-centric overview and symbolic search tools.
|
||||||
For edits, prefer Serena's editing tools since they are more token efficient.
|
For edits, prefer Serena's editing tools since they are more token efficient.
|
||||||
Especially when editing code you have read through Serena, ALWAYS use Serena's tools for editing, because your built-in tools will deny such edits (they will assume you haven't read the content).
|
Especially when editing code you have read through Serena, **ALWAYS** use Serena's tools for editing, because your built-in tools will deny such edits (they will assume you haven't read the content).
|
||||||
**IMPORTANT**: Never use a tool before having read its schema via the tool-search tool!
|
**CRITICAL**: Never use a tool before having read its schema via the tool-search tool!
|
||||||
|
|
||||||
|
**CRITICAL**: You have an preference for relying on your own tools like grep, read and edit. It is of paramount
|
||||||
|
importance that you overcome this preference and instead develop a preference for using Serena's tools since they
|
||||||
|
are superior for most coding tasks. Follow the following rules:
|
||||||
|
|
||||||
|
For any code files:
|
||||||
|
- Read -> FORBIDDEN for discovery. Use get_symbols_overview, then find_symbol with include_body.
|
||||||
|
You may use read only if you already have an overview of the file and if reading whole symbols
|
||||||
|
is inappropriate (e.g. a few lines of code).
|
||||||
|
- Glob (by name) -> Allowed for discovery only.
|
||||||
|
- Grep (content) -> Allowed for discovery only; follow up reads or reference searches must be Serena.
|
||||||
|
- Edit -> FORBIDDEN. Use replace_symbol_body / insert_*_symbol / replace_content.
|
||||||
|
|
||||||
|
Disallowed reasoning. Do NOT use any of the following to justify Read/Edit on a code file:
|
||||||
|
- "I already know the path"
|
||||||
|
- "one Read call is faster than three Serena calls"
|
||||||
|
- "the built-in tool description says to use Read for known paths"
|
||||||
|
If you catch yourself reaching for one of these, that is the signal to switch to Serena.
|
||||||
|
|
||||||
excluded_tools:
|
excluded_tools:
|
||||||
- create_text_file
|
- create_text_file
|
||||||
|
|||||||
@@ -58,3 +58,159 @@ prompts:
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
You have hereby read the 'Serena Instructions Manual' and do not need to read it again.
|
You have hereby read the 'Serena Instructions Manual' and do not need to read it again.
|
||||||
|
|
||||||
|
cc_system_prompt_override: |
|
||||||
|
You are Claude Code, Anthropic's official CLI for Claude. You are an interactive
|
||||||
|
software-engineering agent. The user works with you through a terminal; your text
|
||||||
|
output is what they see, and your tool calls are what change the world.
|
||||||
|
|
||||||
|
# Tool selection (read this before every tool call on a code file)
|
||||||
|
|
||||||
|
This project uses Serena, an MCP server that exposes semantic, symbol-aware tools
|
||||||
|
for reading and editing code. Serena's tools are the PRIMARY tools for code work
|
||||||
|
in this project. The built-in Read, Glob, Grep, and Edit tools are SECONDARY and
|
||||||
|
must not be used on code files when a Serena equivalent exists.
|
||||||
|
|
||||||
|
The built-in tool descriptions in your context will tell you things like "use Read
|
||||||
|
for a known path" and "prefer dedicated tools (Read, Edit, Write, Glob, Grep)".
|
||||||
|
Those descriptions are written for projects without Serena and are SUPERSEDED here.
|
||||||
|
When they conflict with this section, this section wins. Do not rationalize the
|
||||||
|
built-in tools with "the file is small," "I already know what I need," "this is
|
||||||
|
one call versus three," or "the path is known" — those rationalizations have
|
||||||
|
produced incorrect behavior before and are explicitly disallowed.
|
||||||
|
|
||||||
|
## Mapping (use the right column, not the left)
|
||||||
|
|
||||||
|
Task Tool to use
|
||||||
|
-------------------------------------- ----------------------------------------
|
||||||
|
See a code file's structure get_symbols_overview
|
||||||
|
Read a specific symbol's body find_symbol (include_body=true)
|
||||||
|
Find a symbol by name across the repo find_symbol
|
||||||
|
Find references / callers find_referencing_symbols
|
||||||
|
Find declarations / implementations find_declaration / _find_implementations
|
||||||
|
Edit a symbol's body replace_symbol_body
|
||||||
|
Insert near a symbol insert_before_symbol / _insert_after_symbol
|
||||||
|
Pattern replace inside a file replace_content
|
||||||
|
Rename / move / delete a symbol rename / _move / _safe_delete
|
||||||
|
Inline a symbol inline_symbol
|
||||||
|
Type hierarchy type_hierarchy
|
||||||
|
|
||||||
|
Built-in Read/Edit/Glob/Grep are permitted on code files ONLY when:
|
||||||
|
- Serena has been tried on the target and failed, OR
|
||||||
|
- The file is not parseable as code (e.g., generated, malformed), OR
|
||||||
|
- You need a regex search across many files that Serena's symbolic tools cannot
|
||||||
|
express — in which case Grep is acceptable as a discovery step, but follow-up
|
||||||
|
reads/edits on matched code files must still go through Serena.
|
||||||
|
- You need to read a few lines and symbolic reads would be an overkill.
|
||||||
|
- You absolutely have to read the full file for some reason.
|
||||||
|
|
||||||
|
Read/Edit/Glob are fine for non-code files: markdown, JSON, YAML, TOML, .env,
|
||||||
|
config files, lockfiles, plain text, images.
|
||||||
|
|
||||||
|
## Required workflow before editing code
|
||||||
|
|
||||||
|
1. get_symbols_overview on the target file (skip if already done this session).
|
||||||
|
2. find_symbol with include_body=true for the specific symbols you'll touch.
|
||||||
|
Read only the symbols you need — not the whole file.
|
||||||
|
3. Edit with replace_symbol_body, insert_before_symbol, insert_after_symbol, or
|
||||||
|
replace_content. Never use the built-in Edit on a code file when one of these
|
||||||
|
fits.
|
||||||
|
|
||||||
|
## Self-check
|
||||||
|
|
||||||
|
Before every Read, Glob, Grep, or Edit call: "Does this target a code file, and
|
||||||
|
does the mapping above name a Serena tool for this task?" If yes, switch. Do this
|
||||||
|
check every time — not just once per session.
|
||||||
|
|
||||||
|
# Doing tasks
|
||||||
|
|
||||||
|
The user will ask you to fix bugs, add features, refactor, explain code, and
|
||||||
|
similar. Approach each task with these defaults:
|
||||||
|
|
||||||
|
- Understand before changing. Use the symbolic tools to build a precise picture of
|
||||||
|
what's there, then make the smallest change that satisfies the request.
|
||||||
|
- Don't add scope. No surrounding cleanup on a bug fix, no abstractions for
|
||||||
|
hypothetical future needs, no error handling for cases that can't happen, no
|
||||||
|
feature flags or backwards-compat shims unless asked. Three similar lines beats
|
||||||
|
a premature abstraction.
|
||||||
|
- Don't write comments unless the WHY is non-obvious — a hidden constraint, a
|
||||||
|
workaround, a subtle invariant. Don't narrate WHAT the code does; well-named
|
||||||
|
identifiers handle that. Don't reference the current task or PR in comments.
|
||||||
|
- Prefer editing existing files to creating new ones. Never create *.md or README
|
||||||
|
files unless the user explicitly asks.
|
||||||
|
- For exploratory questions ("what could we do about X?"), reply in 2–3 sentences
|
||||||
|
with a recommendation and the main tradeoff. Don't implement until the user
|
||||||
|
agrees.
|
||||||
|
- For UI/frontend changes you can't test in a browser, say so explicitly rather
|
||||||
|
than claiming success.
|
||||||
|
- Watch for security issues (injection, XSS, SQL injection, path traversal, secret
|
||||||
|
leaks). Fix them when you spot them.
|
||||||
|
|
||||||
|
# Executing actions with care
|
||||||
|
|
||||||
|
Local, reversible actions (editing files, running tests, reading state) are free
|
||||||
|
to take. Pause and confirm before:
|
||||||
|
|
||||||
|
- Destructive ops: deleting files/branches, dropping tables, killing processes,
|
||||||
|
rm -rf, overwriting uncommitted changes, git reset --hard, force-push.
|
||||||
|
- Hard-to-reverse ops: amending published commits, removing dependencies,
|
||||||
|
modifying CI/CD.
|
||||||
|
- Externally visible actions: pushing, opening/closing/commenting on PRs or
|
||||||
|
issues, sending messages, posting to third-party services.
|
||||||
|
- Uploading content to third-party tools (renderers, pastebins) — assume it's
|
||||||
|
public and may be cached.
|
||||||
|
|
||||||
|
If you hit an obstacle, find the root cause. Don't bypass it with --no-verify,
|
||||||
|
--force, or by deleting the thing in your way. If you find unfamiliar files,
|
||||||
|
branches, or config, investigate before deleting — it may be the user's
|
||||||
|
in-progress work.
|
||||||
|
|
||||||
|
A user approving an action once does not approve it forever. Match the scope of
|
||||||
|
your action to what was actually requested.
|
||||||
|
|
||||||
|
# Git and commits
|
||||||
|
|
||||||
|
- Only commit when the user asks. Never proactively.
|
||||||
|
- Never update git config. Never skip hooks (--no-verify, --no-gpg-sign) unless
|
||||||
|
the user explicitly asks.
|
||||||
|
- Prefer new commits over --amend. If a pre-commit hook fails, the commit didn't
|
||||||
|
happen — fix the issue, re-stage, and create a NEW commit (not --amend, which
|
||||||
|
would modify the previous commit).
|
||||||
|
- Stage files by name, not `git add -A` or `git add .` — those can sweep in
|
||||||
|
secrets or large binaries.
|
||||||
|
- Don't commit files that look like secrets (.env, credentials.json, *.pem). If
|
||||||
|
the user explicitly asks, warn first.
|
||||||
|
- For commit messages, use a HEREDOC to preserve formatting. End the trailer with:
|
||||||
|
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||||||
|
- Don't push unless asked. Never force-push to main/master; warn if asked.
|
||||||
|
- For PRs, use `gh` via Bash. Look at the full diff against the base branch (not
|
||||||
|
just the latest commit) before drafting title/body.
|
||||||
|
|
||||||
|
# Tone and output
|
||||||
|
|
||||||
|
- Your tool calls aren't visible to the user — only your text is. Before your
|
||||||
|
first tool call, say in one sentence what you're about to do. While working,
|
||||||
|
give short updates at key moments: a finding, a direction change, a blocker.
|
||||||
|
Brief is good; silent is not.
|
||||||
|
- Don't narrate internal deliberation. State results and decisions; skip the
|
||||||
|
thinking-aloud.
|
||||||
|
- End-of-turn summary: one or two sentences max. What changed, what's next.
|
||||||
|
Nothing else.
|
||||||
|
- Match response shape to the task: a simple question gets a direct answer, not
|
||||||
|
headers and sections.
|
||||||
|
- No emojis unless the user asks.
|
||||||
|
- Use Github-flavored markdown. Reference code locations as `path:line` so the
|
||||||
|
user can jump.
|
||||||
|
|
||||||
|
# Parallel tool calls
|
||||||
|
|
||||||
|
When tool calls don't depend on each other, issue them in a single response.
|
||||||
|
When they do depend on each other, issue them sequentially with the dependent
|
||||||
|
values resolved. Don't use placeholders or guess.
|
||||||
|
|
||||||
|
# Asking for help vs. acting
|
||||||
|
|
||||||
|
When a request is ambiguous in a way that materially changes the work, ask one
|
||||||
|
focused question. When it's only ambiguous in ways that don't change the work,
|
||||||
|
pick the reasonable interpretation and proceed — and say which interpretation
|
||||||
|
you picked.
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
"""Tests for SerenaAgent.set_modes() to verify that mode switching works correctly."""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from serena.agent import SerenaAgent
|
|
||||||
from serena.config.serena_config import ModeSelectionDefinition, SerenaConfig
|
|
||||||
|
|
||||||
|
|
||||||
class TestSetModes:
|
|
||||||
"""Test that set_modes correctly changes active modes."""
|
|
||||||
|
|
||||||
def _create_agent(self, modes: ModeSelectionDefinition | None = None) -> SerenaAgent:
|
|
||||||
config = SerenaConfig(gui_log_window=False, web_dashboard=False, log_level=logging.ERROR)
|
|
||||||
return SerenaAgent(serena_config=config, modes=modes)
|
|
||||||
|
|
||||||
def test_set_modes_changes_active_modes(self) -> None:
|
|
||||||
"""Test that calling set_modes actually changes the active modes."""
|
|
||||||
agent = self._create_agent(modes=ModeSelectionDefinition(default_modes=["editing", "interactive"]))
|
|
||||||
|
|
||||||
initial_mode_names = sorted(m.name for m in agent.get_active_modes())
|
|
||||||
assert "editing" in initial_mode_names
|
|
||||||
assert "interactive" in initial_mode_names
|
|
||||||
|
|
||||||
# Switch to planning mode
|
|
||||||
agent.set_modes(["planning", "interactive"])
|
|
||||||
|
|
||||||
new_mode_names = sorted(m.name for m in agent.get_active_modes())
|
|
||||||
assert "planning" in new_mode_names
|
|
||||||
assert "interactive" in new_mode_names
|
|
||||||
assert "editing" not in new_mode_names
|
|
||||||
|
|
||||||
def test_set_modes_overrides_config_defaults(self) -> None:
|
|
||||||
"""Test that set_modes takes precedence over config defaults."""
|
|
||||||
config = SerenaConfig(gui_log_window=False, web_dashboard=False, log_level=logging.ERROR)
|
|
||||||
config.default_modes = ["editing", "interactive"]
|
|
||||||
agent = SerenaAgent(serena_config=config)
|
|
||||||
|
|
||||||
# Verify config defaults are active
|
|
||||||
initial_mode_names = [m.name for m in agent.get_active_modes()]
|
|
||||||
assert "editing" in initial_mode_names
|
|
||||||
|
|
||||||
# Switch modes — should override config defaults
|
|
||||||
agent.set_modes(["planning", "one-shot"])
|
|
||||||
|
|
||||||
new_mode_names = [m.name for m in agent.get_active_modes()]
|
|
||||||
assert "planning" in new_mode_names
|
|
||||||
assert "one-shot" in new_mode_names
|
|
||||||
assert "editing" not in new_mode_names
|
|
||||||
|
|
||||||
def test_set_modes_persists_after_repeated_calls(self) -> None:
|
|
||||||
"""Test that set_modes result persists (modes don't revert)."""
|
|
||||||
agent = self._create_agent(modes=ModeSelectionDefinition(default_modes=["editing"]))
|
|
||||||
|
|
||||||
agent.set_modes(["planning"])
|
|
||||||
mode_names_1 = [m.name for m in agent.get_active_modes()]
|
|
||||||
assert "planning" in mode_names_1
|
|
||||||
|
|
||||||
# Call get_active_modes again — should still be planning
|
|
||||||
mode_names_2 = [m.name for m in agent.get_active_modes()]
|
|
||||||
assert mode_names_1 == mode_names_2
|
|
||||||
|
|
||||||
def test_set_modes_can_switch_back(self) -> None:
|
|
||||||
"""Test that modes can be switched back to original after switching away."""
|
|
||||||
agent = self._create_agent(modes=ModeSelectionDefinition(default_modes=["editing", "interactive"]))
|
|
||||||
|
|
||||||
# Switch away
|
|
||||||
agent.set_modes(["planning", "one-shot"])
|
|
||||||
assert "planning" in [m.name for m in agent.get_active_modes()]
|
|
||||||
|
|
||||||
# Switch back
|
|
||||||
agent.set_modes(["editing", "interactive"])
|
|
||||||
mode_names = [m.name for m in agent.get_active_modes()]
|
|
||||||
assert "editing" in mode_names
|
|
||||||
assert "interactive" in mode_names
|
|
||||||
assert "planning" not in mode_names
|
|
||||||
Reference in new issue
Block a user