mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-12 10:14:08 +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.
|
||||
|
||||
* 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
|
||||
- `GetSymbolsOverviewTool`: ignored paths were not respected in LSP variant (fix in `SolidLanguageServer`)
|
||||
- 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.
|
||||
- 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:
|
||||
- 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.
|
||||
|
||||
@@ -101,8 +101,32 @@ You can do this in Tools / GitHub Copilot / Chat, where at the bottom you can cl
|
||||
|
||||
## Claude Code
|
||||
|
||||
Serena is a great way to make Claude Code both cheaper and more powerful!
|
||||
To add Serena to Claude Code, you can simply run `serena setup claude-code`. Alternatively, follow the instructions below.
|
||||
Serena is a great way to make Claude Code both more efficient and more powerful!
|
||||
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:
|
||||
|
||||
|
||||
@@ -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)
|
||||
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:
|
||||
"""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)
|
||||
|
||||
@@ -12,13 +12,17 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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.path = path
|
||||
self._jinja_template = JinjaTemplate(jinja_template_string.strip())
|
||||
|
||||
def _tostring_exclude_private(self) -> bool:
|
||||
return True
|
||||
|
||||
def get_template_string(self) -> str:
|
||||
return self._jinja_template.get_template_string()
|
||||
|
||||
def render(self, **params: Any) -> str:
|
||||
return self._jinja_template.render(**params)
|
||||
|
||||
@@ -249,6 +253,7 @@ class MultiLangPromptCollection:
|
||||
self,
|
||||
name: str,
|
||||
template_str: str,
|
||||
path: str,
|
||||
lang_code: str = DEFAULT_LANG_CODE,
|
||||
on_name_collision: Literal["skip", "overwrite", "raise"] = "raise",
|
||||
) -> None:
|
||||
@@ -259,7 +264,7 @@ class MultiLangPromptCollection:
|
||||
:param on_name_collision: how to deal with name/lang_code collisions
|
||||
"""
|
||||
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)
|
||||
if mlpt is None:
|
||||
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)
|
||||
elif isinstance(prompt_template_or_list, str):
|
||||
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:
|
||||
raise ValueError(
|
||||
|
||||
@@ -2,7 +2,7 @@ import logging
|
||||
import os
|
||||
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__)
|
||||
|
||||
@@ -23,9 +23,18 @@ class PromptFactoryBase:
|
||||
self.lang_code = lang_code
|
||||
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:
|
||||
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:
|
||||
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 target_module_path: the path to the target module file (.py). Important: The module will be overwritten!
|
||||
"""
|
||||
generated_code = """
|
||||
# ruff: noqa
|
||||
generated_code = """# ruff: noqa
|
||||
# black: skip
|
||||
# mypy: ignore-errors
|
||||
|
||||
# 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 typing import Any
|
||||
|
||||
@@ -68,6 +75,9 @@ class PromptFactory(PromptFactoryBase):
|
||||
else:
|
||||
method_params_str = ", *, " + ", ".join([f"{param}: Any" for param in template_parameters])
|
||||
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:
|
||||
return self._render_prompt('{template_name}', locals())
|
||||
"""
|
||||
|
||||
@@ -869,18 +869,6 @@ class SerenaAgent:
|
||||
raise ValueError("No active project. Please activate a project first.")
|
||||
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]:
|
||||
"""
|
||||
:return: the list of active modes
|
||||
|
||||
+58
-8
@@ -18,7 +18,6 @@ from sensai.util.string import dict_string
|
||||
from tqdm import tqdm
|
||||
|
||||
from serena import serena_version
|
||||
from serena.agent import SerenaAgent
|
||||
from serena.config.client_setup import client_setup_handlers
|
||||
from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode
|
||||
from serena.config.serena_config import (
|
||||
@@ -36,9 +35,7 @@ from serena.constants import (
|
||||
SERENAS_OWN_CONTEXT_YAMLS_DIR,
|
||||
SERENAS_OWN_MODE_YAMLS_DIR,
|
||||
)
|
||||
from serena.mcp import SerenaMCPFactory
|
||||
from serena.project import Project
|
||||
from serena.tools import FindReferencingSymbolsTool, FindSymbolTool, GetSymbolsOverviewTool, SearchForPatternTool, ToolRegistry
|
||||
from serena.prompt_factory import SerenaPromptFactory
|
||||
from serena.util.cli_util import AutoRegisteringGroup
|
||||
from serena.util.dataclass import get_dataclass_default
|
||||
from serena.util.logging import MemoryLogHandler
|
||||
@@ -315,6 +312,8 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
trace_lsp_communication: bool | None,
|
||||
tool_timeout: float | None,
|
||||
) -> None:
|
||||
from serena.mcp import SerenaMCPFactory
|
||||
|
||||
# initialize logging, using INFO level initially (will later be adjusted by SerenaAgent according to the config)
|
||||
# * 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)
|
||||
@@ -346,6 +345,7 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
log.warning("No project root found from %s; not activating any project", os.getcwd())
|
||||
|
||||
project_file = project_file_arg or project
|
||||
|
||||
factory = SerenaMCPFactory(context=context, project=project_file, memory_log_handler=memory_log_handler)
|
||||
server = factory.create_mcp_server(
|
||||
host=host,
|
||||
@@ -394,6 +394,8 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
def print_system_prompt(
|
||||
project: str, log_level: str, only_instructions: bool, context: str, modes: Sequence[str] | 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."
|
||||
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
|
||||
if 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(
|
||||
project=os.path.abspath(project),
|
||||
serena_config=SerenaConfig(web_dashboard=False, log_level=lvl),
|
||||
serena_config=serena_config,
|
||||
context=context_instance,
|
||||
modes=modes_selection_def,
|
||||
)
|
||||
@@ -828,6 +835,8 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
:param path: The path to check.
|
||||
: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()
|
||||
proj = Project.load(os.path.abspath(project), serena_config=serena_config)
|
||||
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 verbose: if set, prints detailed information about the indexed symbols.
|
||||
"""
|
||||
from serena.project import Project
|
||||
|
||||
serena_config = SerenaConfig.from_config_file()
|
||||
proj = Project.load(os.path.abspath(project), serena_config=serena_config)
|
||||
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.
|
||||
"""
|
||||
# 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)
|
||||
project_path = os.path.abspath(project)
|
||||
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("--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:
|
||||
from serena.tools import ToolRegistry
|
||||
|
||||
tool_registry = ToolRegistry()
|
||||
if quiet:
|
||||
if only_optional:
|
||||
@@ -1062,6 +1079,9 @@ class ToolCommands(AutoRegisteringGroup):
|
||||
@click.argument("tool_name", type=str)
|
||||
@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:
|
||||
from serena.agent import SerenaAgent
|
||||
from serena.mcp import SerenaMCPFactory
|
||||
|
||||
# Load the context
|
||||
serena_context = None
|
||||
if context:
|
||||
@@ -1089,16 +1109,26 @@ class PromptCommands(AutoRegisteringGroup):
|
||||
|
||||
@staticmethod
|
||||
@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:
|
||||
# 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")]
|
||||
for prompt_yaml_name in serena_prompt_yaml_names:
|
||||
user_prompt_yaml_path = PromptCommands._get_user_prompt_yaml_path(prompt_yaml_name)
|
||||
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:
|
||||
click.echo(prompt_yaml_name)
|
||||
click.echo(f" * {prompt_yaml_name}")
|
||||
|
||||
@staticmethod
|
||||
@click.command(
|
||||
@@ -1171,6 +1201,26 @@ class PromptCommands(AutoRegisteringGroup):
|
||||
os.remove(user_prompt_yaml_path)
|
||||
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()
|
||||
_context = ContextCommands()
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
# 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 typing import Any
|
||||
|
||||
@@ -14,15 +13,27 @@ class PromptFactory(PromptFactoryBase):
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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(
|
||||
self,
|
||||
*,
|
||||
@@ -33,3 +44,9 @@ class PromptFactory(PromptFactoryBase):
|
||||
mode_system_prompts: Any,
|
||||
) -> str:
|
||||
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
|
||||
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.
|
||||
**IMPORTANT**: If you haven't seen Serena's tools yet, you should use the tool search tool to load all of them
|
||||
right now.
|
||||
**CRITICAL**: If you haven't seen Serena's tools yet, you should use the tool search tool to load all of them
|
||||
**right now**.
|
||||
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.
|
||||
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!
|
||||
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).
|
||||
**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:
|
||||
- create_text_file
|
||||
|
||||
@@ -58,3 +58,159 @@ prompts:
|
||||
{% endfor %}
|
||||
|
||||
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