Merge branch 'dev-v1.2.0'

This commit is contained in:
Dominik Jain committed 2026-04-27 13:35:18 +02:00
commit 4e0043a9b8
13 files changed
+347 -111

No files matched your search

+7 -1
View File
@@ -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.
+26 -2
View File
@@ -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:
+27
View File
@@ -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>
+4
View File
@@ -0,0 +1,4 @@
from serena.cli import top_level
if __name__ == "__main__":
top_level(["--help"])
+3
View File
@@ -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)
+8 -3
View File
@@ -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(
+15 -5
View File
@@ -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())
"""
-12
View File
@@ -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
View File
@@ -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.
-75
View File
@@ -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