mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-11 03:13:51 +00:00
Merge remote-tracking branch 'origin/main' into features/diagnostics_and_ls_extensions
Conflicts: CHANGELOG.md
This commit is contained in:
commit
90d6f2cde8
18 files changed
+283
-192
No files matched your search
+25
-49
@@ -3,15 +3,18 @@ project_name: "serena"
|
||||
|
||||
|
||||
# list of languages for which language servers are started; choose from:
|
||||
# al bash clojure cpp csharp
|
||||
# csharp_omnisharp dart elixir elm erlang
|
||||
# fortran fsharp go groovy haskell
|
||||
# java julia kotlin lua markdown
|
||||
# matlab nix pascal perl php
|
||||
# php_phpactor powershell python python_jedi r
|
||||
# rego ruby ruby_solargraph rust scala
|
||||
# swift terraform toml typescript typescript_vts
|
||||
# vue yaml zig
|
||||
# al ansible bash clojure cpp
|
||||
# cpp_ccls crystal csharp csharp_omnisharp dart
|
||||
# elixir elm erlang fortran fsharp
|
||||
# go groovy haskell haxe hlsl
|
||||
# java json julia kotlin lean4
|
||||
# lua luau markdown matlab msl
|
||||
# nix ocaml pascal perl php
|
||||
# php_phpactor powershell python python_jedi python_ty
|
||||
# r rego ruby ruby_solargraph rust
|
||||
# scala solidity swift systemverilog terraform
|
||||
# toml typescript typescript_vts vue yaml
|
||||
# zig
|
||||
# (This list may be outdated. For the current list, see values of Language enum here:
|
||||
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
|
||||
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
|
||||
@@ -45,49 +48,12 @@ read_only: false
|
||||
|
||||
# list of tool names to exclude.
|
||||
# This extends the existing exclusions (e.g. from the global configuration)
|
||||
#
|
||||
# Below is the complete list of tools for convenience.
|
||||
# To make sure you have the latest list of tools, and to view their descriptions,
|
||||
# execute `uv run scripts/print_tool_overview.py`.
|
||||
# * `activate_project`: Activates a project based on the project name or path.
|
||||
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
||||
# * `create_text_file`: Creates/overwrites a file in the project directory.
|
||||
# * `delete_memory`: Delete a memory file. Should only happen if a user asks for it explicitly,
|
||||
# for example by saying that the information retrieved from a memory file is no longer correct
|
||||
# or no longer relevant for the project.
|
||||
# * `edit_memory`: Replaces content matching a regular expression in a memory.
|
||||
# * `execute_shell_command`: Executes a shell command.
|
||||
# * `find_file`: Finds files in the given relative paths
|
||||
# * `find_referencing_symbols`: Finds symbols that reference the given symbol using the language server backend
|
||||
# * `find_symbol`: Performs a global (or local) search using the language server backend.
|
||||
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
||||
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
|
||||
# * `initial_instructions`: Provides instructions Serena usage (i.e. the 'Serena Instructions Manual')
|
||||
# for clients that do not read the initial instructions when the MCP server is connected.
|
||||
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
|
||||
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
|
||||
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
|
||||
# * `list_memories`: List available memories. Any memory can be read using the `read_memory` tool.
|
||||
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
|
||||
# * `read_file`: Reads a file within the project directory.
|
||||
# * `read_memory`: Read the content of a memory file. This tool should only be used if the information
|
||||
# is relevant to the current task. You can infer whether the information
|
||||
# is relevant from the memory file name.
|
||||
# You should not read the same memory file multiple times in the same conversation.
|
||||
# * `rename_memory`: Renames or moves a memory. Moving between project and global scope is supported
|
||||
# (e.g., renaming "global/foo" to "bar" moves it from global to project scope).
|
||||
# * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities.
|
||||
# For JB, we use a separate tool.
|
||||
# * `replace_content`: Replaces content in a file (optionally using regular expressions).
|
||||
# * `replace_symbol_body`: Replaces the full definition of a symbol using the language server backend.
|
||||
# * `safe_delete_symbol`:
|
||||
# * `search_for_pattern`: Performs a search for a pattern in the project.
|
||||
# * `write_memory`: Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format.
|
||||
# The memory name should be meaningful.
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
excluded_tools: []
|
||||
|
||||
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
|
||||
# This extends the existing inclusions (e.g. from the global configuration).
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
included_optional_tools: []
|
||||
|
||||
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||
@@ -122,14 +88,17 @@ encoding: utf-8
|
||||
base_modes:
|
||||
|
||||
# list of mode names that are to be activated by default.
|
||||
# The full set of modes to be activated is base_modes + default_modes.
|
||||
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
|
||||
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
|
||||
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
|
||||
# This setting can, in turn, be overridden by CLI parameters (--mode).
|
||||
# Set this to [] to not use the default modes defined in the global config for this project.
|
||||
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
|
||||
default_modes:
|
||||
|
||||
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
|
||||
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
fixed_tools: []
|
||||
|
||||
# time budget (seconds) per tool call for the retrieval of additional symbol information
|
||||
@@ -167,3 +136,10 @@ ignored_memory_patterns: []
|
||||
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
|
||||
# No documentation on options means no options are available.
|
||||
ls_specific_settings: {}
|
||||
|
||||
# list of mode names to be activated additionally for this project.
|
||||
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
|
||||
# Otherwise, this setting overrides the global configuration.
|
||||
# Set this to a list of mode names to always include the respective modes for this project.
|
||||
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
|
||||
added_modes:
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
Status of the `main` branch. Changes prior to the next official version change will appear here.
|
||||
|
||||
* General:
|
||||
- Breaking change in mode definitions: Projects (project.yml) can no longer override `base_modes`.
|
||||
Instead, they can define `added_modes` to add modes on top of base and default modes.
|
||||
See updated [documentation on modes](https://oraios.github.io/serena/02-usage/050_configuration.html#modes).
|
||||
- Serena's default configuration now uses `interactive` and `editing` as `base_modes` instead of as `default_modes`.
|
||||
|
||||
* JetBrains:
|
||||
- Add new tools:
|
||||
- `jet_brains_list_inspections`: Lists available IDE inspections (akin to diagnostics), optionally filtered by language or group
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</p>
|
||||
|
||||
<h3 align="center">
|
||||
Serena is the IDE for your coding agent.
|
||||
The IDE for Your Coding Agent
|
||||
</h3>
|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -118,32 +118,26 @@ Examples of built-in modes include:
|
||||
|
||||
Find the concrete definitions of these modes [here](https://github.com/oraios/serena/tree/main/src/serena/resources/config/modes).
|
||||
|
||||
Active modes are configured in (from lowest to highest precedence):
|
||||
The modes to be activated are configured in:
|
||||
* the global configuration file (`serena_config.yml`)
|
||||
- defines `base_modes`, which are always included
|
||||
- defines `default_modes`, which can be overridden by projects or command line parameters
|
||||
* the project configuration file (`project.yml`)
|
||||
- defines `default_modes` (overriding the default modes in the global configuration)
|
||||
- defines `added_modes`, which are added on top
|
||||
* at startup via command-line parameters
|
||||
- can override default modes with `--mode`
|
||||
- can define modes to be added on top with `--add-mode`
|
||||
|
||||
The two former sources define both **base modes** and **default modes**.
|
||||
Ultimately, the active modes are the union of base modes and default modes (after applying all overrides).
|
||||
Command-line parameters override default modes but not base modes.
|
||||
Base modes should thus be used to define modes that you always want to be active, regardless of command-line parameters.
|
||||
Ultimately, the active modes are given by the union of
|
||||
* `base_modes` defined in the global configuration (always active)
|
||||
* `default_modes` (defined in the global configuration, optionally overridden by the project/CLI)
|
||||
* `added_modes` (defined in the project configuration/via CLI parameters)
|
||||
|
||||
Command-line parameters for overriding default modes:
|
||||
When launching the MCP sever, specify modes using `--mode <mode-name>`; multiple modes can be specified, e.g. `--mode planning --mode no-onboarding`.
|
||||
|
||||
:::{important}
|
||||
By default, Serena activates the two modes `interactive` and `editing` (as defined in the global configuration).
|
||||
|
||||
As soon as you start to specify modes via the command line, only the modes you explicitly specify will be active, however.
|
||||
Therefore, if you want to keep the default modes, you must specify them as well.
|
||||
For example, to add mode `no-memories` to the default behaviour, specify
|
||||
```shell
|
||||
--mode interactive --mode editing --mode no-memories
|
||||
```
|
||||
|
||||
If you want to keep certain modes as always active, regardless of command-line parameters,
|
||||
define them as *base modes* in the global or project configuration.
|
||||
:::
|
||||
So you should
|
||||
* define modes you definitely always want to use in `base_modes`,
|
||||
* define modes that you typically want to use but sometimes want to override in `default_modes`,
|
||||
* use `added_modes` to add modes that you need only for specific projects/sessions.
|
||||
|
||||
:::{note}
|
||||
**Mode Compatibility**: While you can combine modes, some may be semantically incompatible (e.g., `interactive` and `one-shot`).
|
||||
|
||||
+35
-14
@@ -31,6 +31,8 @@ from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode
|
||||
from serena.config.serena_config import (
|
||||
LanguageBackend,
|
||||
ModeSelectionDefinition,
|
||||
ModeSelectionDefinitionWithAddedModes,
|
||||
ModeSelectionDefinitionWithBaseModes,
|
||||
NamedToolInclusionDefinition,
|
||||
RegisteredProject,
|
||||
SerenaConfig,
|
||||
@@ -206,18 +208,36 @@ class ActiveModes:
|
||||
def __init__(self) -> None:
|
||||
self._configured_base_modes: Sequence[str] | None = None
|
||||
self._configured_default_modes: Sequence[str] | None = None
|
||||
self._added_modes: set[str] = set()
|
||||
self._dynamically_activated_mode_names: set[str] = set()
|
||||
"""
|
||||
the subset of active mode names that are dynamically activated (not necessarily enabled after project change)
|
||||
"""
|
||||
self._active_mode_names: Sequence[str] = []
|
||||
"""
|
||||
the full list of active mode names
|
||||
"""
|
||||
|
||||
def apply(self, mode_selection: ModeSelectionDefinition) -> None:
|
||||
log.debug("Applying mode selection definition %s", mode_selection)
|
||||
|
||||
# apply overrides
|
||||
log.debug("Applying mode selection: default_modes=%s, base_modes=%s", mode_selection.default_modes, mode_selection.base_modes)
|
||||
if mode_selection.base_modes is not None:
|
||||
self._configured_base_modes = mode_selection.base_modes
|
||||
if isinstance(mode_selection, ModeSelectionDefinitionWithBaseModes):
|
||||
if mode_selection.base_modes is not None:
|
||||
self._configured_base_modes = mode_selection.base_modes
|
||||
if mode_selection.default_modes is not None:
|
||||
self._configured_default_modes = mode_selection.default_modes
|
||||
log.debug("Current mode selection: base_modes=%s, default_modes=%s", self._configured_base_modes, self._configured_default_modes)
|
||||
|
||||
self._active_mode_names = sorted(set(self._configured_base_modes or []) | set(self._configured_default_modes or []))
|
||||
# apply added modes (if any)
|
||||
if isinstance(mode_selection, ModeSelectionDefinitionWithAddedModes):
|
||||
if mode_selection.added_modes:
|
||||
log.debug("Adding modes: %s", mode_selection.added_modes)
|
||||
self._added_modes.update(mode_selection.added_modes)
|
||||
log.debug("Current added modes: %s", self._added_modes)
|
||||
|
||||
self._dynamically_activated_mode_names = set(self._configured_default_modes or []) | self._added_modes
|
||||
self._active_mode_names = sorted(set(self._configured_base_modes or []) | self._dynamically_activated_mode_names)
|
||||
|
||||
def get_mode_names(self) -> Sequence[str]:
|
||||
return self._active_mode_names
|
||||
@@ -231,8 +251,8 @@ class ActiveModes:
|
||||
def get_modes(self) -> Sequence[SerenaAgentMode]:
|
||||
return [self.get_mode_instance(mode_name) for mode_name in self._active_mode_names]
|
||||
|
||||
def get_default_modes(self) -> Sequence[SerenaAgentMode]:
|
||||
return [self.get_mode_instance(mode_name) for mode_name in self._configured_default_modes or []]
|
||||
def get_dynamically_activated_modes(self) -> Sequence[SerenaAgentMode]:
|
||||
return [self.get_mode_instance(mode_name) for mode_name in self._dynamically_activated_mode_names]
|
||||
|
||||
def get_base_modes(self) -> Sequence[SerenaAgentMode]:
|
||||
return [self.get_mode_instance(mode_name) for mode_name in self._configured_base_modes or []]
|
||||
@@ -511,8 +531,7 @@ class SerenaAgent:
|
||||
:param serena_config: the Serena configuration or None to read the configuration from the default location.
|
||||
:param context: the context in which the agent is operating, None for default context.
|
||||
The context may adjust prompts, tool availability, and tool descriptions.
|
||||
:param modes: list of modes in which the agent is operating (they will be combined), None for default modes.
|
||||
The modes may adjust prompts, tool availability, and tool descriptions.
|
||||
:param modes: mode selection definition to apply for this session
|
||||
:param memory_log_handler: a MemoryLogHandler instance from which to read log messages; if None, a new one will be created
|
||||
if necessary.
|
||||
"""
|
||||
@@ -521,7 +540,7 @@ class SerenaAgent:
|
||||
self._gui_log_viewer: Optional["GuiLogViewer"] = None
|
||||
self._dashboard_manager: DashboardManager | None = None
|
||||
self._project_prompt_status = ProjectPromptProvisionStatus()
|
||||
self._mode_overrides = modes
|
||||
self._session_mode_selection_definition = modes
|
||||
self.version = serena_version()
|
||||
|
||||
# obtain serena configuration using the decoupled factory function
|
||||
@@ -712,9 +731,11 @@ class SerenaAgent:
|
||||
# * base modes: These cannot be changed, so they are fully applied
|
||||
for base_mode in modes.get_base_modes():
|
||||
tool_inclusion_definitions.append(base_mode)
|
||||
# * default modes: When not in a single-project context, these modes are dynamic (can later be turned off),
|
||||
# so we consider only their inclusions (but not their exclusions, because these must not be hard)
|
||||
for mode in modes.get_default_modes():
|
||||
# * dynamically activated modes:
|
||||
# - When not in a single-project context, these modes can later be turned off,
|
||||
# so we consider only their inclusions (but not their exclusions, because these must not be hard).
|
||||
# - In a single-project context, we can consider them fully.
|
||||
for mode in modes.get_dynamically_activated_modes():
|
||||
if is_single_project:
|
||||
tool_inclusion_definitions.append(mode)
|
||||
else:
|
||||
@@ -986,8 +1007,8 @@ class SerenaAgent:
|
||||
self._active_modes.apply(self.serena_config)
|
||||
if self._active_project:
|
||||
self._active_modes.apply(self._active_project.project_config)
|
||||
if self._mode_overrides:
|
||||
self._active_modes.apply(self._mode_overrides)
|
||||
if self._session_mode_selection_definition:
|
||||
self._active_modes.apply(self._session_mode_selection_definition)
|
||||
if log_message:
|
||||
active_mode_names = self._active_modes.get_mode_names()
|
||||
log.info(f"Active modes ({len(active_mode_names)}): {', '.join(active_mode_names)}")
|
||||
|
||||
+26
-10
@@ -23,6 +23,7 @@ from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode
|
||||
from serena.config.serena_config import (
|
||||
LanguageBackend,
|
||||
ModeSelectionDefinition,
|
||||
ModeSelectionDefinitionWithAddedModes,
|
||||
ProjectConfig,
|
||||
RegisteredProject,
|
||||
SerenaConfig,
|
||||
@@ -37,7 +38,6 @@ from serena.constants import (
|
||||
)
|
||||
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
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
@@ -46,15 +46,17 @@ from solidlsp.util.subprocess_util import subprocess_kwargs
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_MAX_CONTENT_WIDTH = 200
|
||||
_MODES_EXPLANATION = f"""\b\nBuilt-in mode names or paths to custom mode YAMLs with which to
|
||||
override the default modes defined in the global Serena configuration or
|
||||
_MODES_EXPLANATION = """\b\nBuilt-in mode names or paths to custom mode YAMLs with which to
|
||||
override the default_modes defined in the global Serena configuration or
|
||||
the active project.
|
||||
For details on mode configuration, see
|
||||
https://oraios.github.io/serena/02-usage/050_configuration.html#modes.
|
||||
If no configuration changes were made, the base defaults are:
|
||||
{get_dataclass_default(SerenaConfig, "default_modes")}.
|
||||
Overriding them means that they no longer apply, so you will need to
|
||||
re-specify them in addition to further modes if you want to keep them."""
|
||||
"""
|
||||
_ADD_MODES_EXPLANATION = """\b\nMode names or paths to custom mode YAMLs which shall
|
||||
be added on top of the other modes specified by the global/project configuration.
|
||||
For details on mode configuration, see
|
||||
https://oraios.github.io/serena/02-usage/050_configuration.html#modes.
|
||||
"""
|
||||
|
||||
|
||||
def find_project_root(root: str | Path | None = None) -> str | None:
|
||||
@@ -228,13 +230,22 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
)
|
||||
@click.option(
|
||||
"--mode",
|
||||
"modes",
|
||||
"default_modes",
|
||||
type=str,
|
||||
multiple=True,
|
||||
default=(),
|
||||
show_default=False,
|
||||
help=_MODES_EXPLANATION,
|
||||
)
|
||||
@click.option(
|
||||
"--add-mode",
|
||||
"added_modes",
|
||||
type=str,
|
||||
multiple=True,
|
||||
default=(),
|
||||
show_default=False,
|
||||
help=_ADD_MODES_EXPLANATION,
|
||||
)
|
||||
@click.option(
|
||||
"--language-backend",
|
||||
type=click.Choice([lb.value for lb in LanguageBackend]),
|
||||
@@ -300,7 +311,8 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
project_file_arg: str | None,
|
||||
project_from_cwd: bool | None,
|
||||
context: str,
|
||||
modes: Sequence[str],
|
||||
default_modes: Sequence[str],
|
||||
added_modes: Sequence[str],
|
||||
language_backend: str | None,
|
||||
transport: Literal["stdio", "sse", "streamable-http"],
|
||||
host: str,
|
||||
@@ -346,11 +358,15 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
|
||||
project_file = project_file_arg or project
|
||||
|
||||
mode_selection_def: ModeSelectionDefinition | None = None
|
||||
if default_modes or added_modes:
|
||||
mode_selection_def = ModeSelectionDefinitionWithAddedModes(default_modes=default_modes or None, added_modes=added_modes or None)
|
||||
|
||||
factory = SerenaMCPFactory(context=context, project=project_file, memory_log_handler=memory_log_handler)
|
||||
server = factory.create_mcp_server(
|
||||
host=host,
|
||||
port=port,
|
||||
modes=modes,
|
||||
mode_selection_def=mode_selection_def,
|
||||
language_backend=LanguageBackend.from_str(language_backend) if language_backend else None,
|
||||
enable_web_dashboard=enable_web_dashboard,
|
||||
open_web_dashboard=open_web_dashboard,
|
||||
|
||||
@@ -168,10 +168,22 @@ class NamedToolInclusionDefinition(ToolInclusionDefinition):
|
||||
|
||||
@dataclass
|
||||
class ModeSelectionDefinition:
|
||||
base_modes: Sequence[str] | None = None
|
||||
default_modes: Sequence[str] | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ModeSelectionDefinitionWithBaseModes(ModeSelectionDefinition):
|
||||
base_modes: Sequence[str] | None = ("interactive", "editing")
|
||||
"""
|
||||
the base modes to use, which are always guaranteed to be included
|
||||
"""
|
||||
|
||||
|
||||
@dataclass
|
||||
class ModeSelectionDefinitionWithAddedModes(ModeSelectionDefinition):
|
||||
added_modes: Sequence[str] | None = None
|
||||
|
||||
|
||||
class LanguageBackend(Enum):
|
||||
LSP = "LSP"
|
||||
"""
|
||||
@@ -228,7 +240,7 @@ class LineEnding(Enum):
|
||||
|
||||
|
||||
@dataclass
|
||||
class SharedConfig(ModeSelectionDefinition, ToolInclusionDefinition, ToStringMixin):
|
||||
class SharedConfig(ToolInclusionDefinition, ToStringMixin):
|
||||
"""Shared between SerenaConfig and ProjectConfig, the latter used to override values in the form
|
||||
(same as in ModeSelectionDefinition).
|
||||
The defaults here shall be none and should be set to the global default values in SerenaConfig.
|
||||
@@ -255,7 +267,7 @@ Uses $projectDir and $projectFolderName as placeholders.
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class ProjectConfig(SharedConfig):
|
||||
class ProjectConfig(SharedConfig, ModeSelectionDefinitionWithAddedModes):
|
||||
project_name: str
|
||||
languages: list[Language]
|
||||
ignored_paths: list[str] = field(default_factory=list)
|
||||
@@ -471,6 +483,9 @@ class ProjectConfig(SharedConfig):
|
||||
excluded_tools = data["excluded_tools"] or []
|
||||
included_optional_tools = data["included_optional_tools"] or []
|
||||
|
||||
if "base_modes" in data and data["base_modes"] is not None:
|
||||
log.warning("The base_modes setting in project.yml is deprecated and will be ignored.")
|
||||
|
||||
return cls(
|
||||
project_name=data["project_name"],
|
||||
languages=languages,
|
||||
@@ -486,7 +501,7 @@ class ProjectConfig(SharedConfig):
|
||||
encoding=data["encoding"],
|
||||
line_ending=line_ending,
|
||||
language_backend=language_backend,
|
||||
base_modes=data["base_modes"],
|
||||
added_modes=data["added_modes"],
|
||||
default_modes=data["default_modes"],
|
||||
symbol_info_budget=symbol_info_budget,
|
||||
ls_specific_settings=data.get("ls_specific_settings", {}),
|
||||
@@ -677,7 +692,7 @@ class RegisteredProject(ToStringMixin):
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class SerenaConfig(SharedConfig):
|
||||
class SerenaConfig(SharedConfig, ModeSelectionDefinitionWithBaseModes):
|
||||
"""
|
||||
Holds the Serena agent configuration, which is typically loaded from a YAML configuration file
|
||||
(when instantiated via :method:`from_config_file`), which is updated when projects are added or removed.
|
||||
@@ -731,7 +746,6 @@ class SerenaConfig(SharedConfig):
|
||||
"""
|
||||
the language backend to use for code understanding features
|
||||
"""
|
||||
default_modes: Sequence[str] | None = ("interactive", "editing")
|
||||
line_ending: LineEnding = LineEnding.NATIVE
|
||||
symbol_info_budget: float = 10.0
|
||||
"""
|
||||
|
||||
+3
-6
@@ -3,7 +3,7 @@ The Serena Model Context Protocol (MCP) Server
|
||||
"""
|
||||
|
||||
import sys
|
||||
from collections.abc import AsyncIterator, Iterator, Sequence
|
||||
from collections.abc import AsyncIterator, Iterator
|
||||
from contextlib import asynccontextmanager
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass
|
||||
@@ -272,7 +272,7 @@ class SerenaMCPFactory:
|
||||
self,
|
||||
host: str = "127.0.0.1",
|
||||
port: int = 8000,
|
||||
modes: Sequence[str] = (),
|
||||
mode_selection_def: ModeSelectionDefinition | None = None,
|
||||
language_backend: LanguageBackend | None = None,
|
||||
enable_web_dashboard: bool | None = None,
|
||||
enable_gui_log_window: bool | None = None,
|
||||
@@ -286,7 +286,7 @@ class SerenaMCPFactory:
|
||||
|
||||
:param host: The host to bind to
|
||||
:param port: The port to bind to
|
||||
:param modes: List of mode names or paths to mode files
|
||||
:param mode_selection_def: the mode selection definition to apply
|
||||
:param language_backend: the language backend to use, overriding the configuration setting.
|
||||
:param enable_web_dashboard: Whether to enable the web dashboard. If not specified, will take the value from the serena configuration.
|
||||
:param enable_gui_log_window: Whether to enable the GUI log window. It currently does not work on macOS, and setting this to True will be ignored then.
|
||||
@@ -318,9 +318,6 @@ class SerenaMCPFactory:
|
||||
if language_backend is not None:
|
||||
config.language_backend = language_backend
|
||||
|
||||
mode_selection_def: ModeSelectionDefinition | None = None
|
||||
if modes:
|
||||
mode_selection_def = ModeSelectionDefinition(default_modes=modes)
|
||||
self.agent = self._create_serena_agent(config, mode_selection_def)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
description: JetBrains tools replace language server-based tools
|
||||
prompt: |
|
||||
You have access to the very powerful JetBrains tools for symbolic operations.
|
||||
These party replace regular tools you were informed about:
|
||||
These partly replace regular tools you were informed about:
|
||||
* `jet_brains_find_symbol` replaces `find_symbol`
|
||||
* `jet_brains_find_referencing_symbols` replaces `find_referencing_symbols`
|
||||
* `jet_brains_get_symbols_overview` replaces `get_symbols_overview`
|
||||
* `jet_brains_rename` replaces `rename_symbol`
|
||||
excluded_tools:
|
||||
- find_symbol
|
||||
- find_referencing_symbols
|
||||
|
||||
@@ -3,16 +3,18 @@ project_name: "project_name"
|
||||
|
||||
|
||||
# list of languages for which language servers are started; choose from:
|
||||
# al bash clojure cpp csharp
|
||||
# csharp_omnisharp dart elixir elm erlang
|
||||
# fortran fsharp go groovy haskell
|
||||
# haxe java julia kotlin lua
|
||||
# markdown
|
||||
# matlab nix pascal perl php
|
||||
# php_phpactor powershell python python_jedi r
|
||||
# rego ruby ruby_solargraph rust scala
|
||||
# swift terraform toml typescript typescript_vts
|
||||
# vue yaml zig
|
||||
# al ansible bash clojure cpp
|
||||
# cpp_ccls crystal csharp csharp_omnisharp dart
|
||||
# elixir elm erlang fortran fsharp
|
||||
# go groovy haskell haxe hlsl
|
||||
# java json julia kotlin lean4
|
||||
# lua luau markdown matlab msl
|
||||
# nix ocaml pascal perl php
|
||||
# php_phpactor powershell python python_jedi python_ty
|
||||
# r rego ruby ruby_solargraph rust
|
||||
# scala solidity swift systemverilog terraform
|
||||
# toml typescript typescript_vts vue yaml
|
||||
# zig
|
||||
# (This list may be outdated. For the current list, see values of Language enum here:
|
||||
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
|
||||
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
|
||||
@@ -65,71 +67,34 @@ read_only: false
|
||||
|
||||
# list of tool names to exclude.
|
||||
# This extends the existing exclusions (e.g. from the global configuration)
|
||||
#
|
||||
# Below is the complete list of tools for convenience.
|
||||
# To make sure you have the latest list of tools, and to view their descriptions,
|
||||
# execute `uv run scripts/print_tool_overview.py`.
|
||||
#
|
||||
# * `activate_project`: Activates a project based on the project name or path.
|
||||
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
||||
# * `create_text_file`: Creates/overwrites a file in the project directory.
|
||||
# * `delete_memory`: Delete a memory file. Should only happen if a user asks for it explicitly,
|
||||
# for example by saying that the information retrieved from a memory file is no longer correct
|
||||
# or no longer relevant for the project.
|
||||
# * `edit_memory`: Replaces content matching a regular expression in a memory.
|
||||
# * `execute_shell_command`: Executes a shell command.
|
||||
# * `find_file`: Finds files in the given relative paths
|
||||
# * `find_referencing_symbols`: Finds symbols that reference the given symbol using the language server backend
|
||||
# * `find_symbol`: Performs a global (or local) search using the language server backend.
|
||||
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
||||
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
|
||||
# * `initial_instructions`: Provides instructions Serena usage (i.e. the 'Serena Instructions Manual')
|
||||
# for clients that do not read the initial instructions when the MCP server is connected.
|
||||
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
|
||||
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
|
||||
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
|
||||
# * `list_memories`: List available memories. Any memory can be read using the `read_memory` tool.
|
||||
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
|
||||
# * `read_file`: Reads a file within the project directory.
|
||||
# * `read_memory`: Read the content of a memory file. This tool should only be used if the information
|
||||
# is relevant to the current task. You can infer whether the information
|
||||
# is relevant from the memory file name.
|
||||
# You should not read the same memory file multiple times in the same conversation.
|
||||
# * `rename_memory`: Renames or moves a memory. Moving between project and global scope is supported
|
||||
# (e.g., renaming "global/foo" to "bar" moves it from global to project scope).
|
||||
# * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities.
|
||||
# For JB, we use a separate tool.
|
||||
# * `replace_content`: Replaces content in a file (optionally using regular expressions).
|
||||
# * `replace_symbol_body`: Replaces the full definition of a symbol using the language server backend.
|
||||
# * `safe_delete_symbol`:
|
||||
# * `search_for_pattern`: Performs a search for a pattern in the project.
|
||||
# * `write_memory`: Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format.
|
||||
# The memory name should be meaningful.
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
excluded_tools: []
|
||||
|
||||
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
|
||||
# This extends the existing inclusions (e.g. from the global configuration).
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
included_optional_tools: []
|
||||
|
||||
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
|
||||
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
fixed_tools: []
|
||||
|
||||
# list of mode names to that are always to be included in the set of active modes
|
||||
# The full set of modes to be activated is base_modes + default_modes.
|
||||
# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
|
||||
# Otherwise, this setting overrides the global configuration.
|
||||
# Set this to [] to disable base modes for this project.
|
||||
# Set this to a list of mode names to always include the respective modes for this project.
|
||||
base_modes:
|
||||
|
||||
# list of mode names that are to be activated by default.
|
||||
# The full set of modes to be activated is base_modes + default_modes.
|
||||
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
|
||||
# list of mode names that are to be activated by default, overriding the setting in the global configuration.
|
||||
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
|
||||
# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply.
|
||||
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
|
||||
# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply
|
||||
# for this project.
|
||||
# This setting can, in turn, be overridden by CLI parameters (--mode).
|
||||
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
|
||||
default_modes:
|
||||
|
||||
# list of mode names to be activated additionally for this project, e.g. ["query-projects"]
|
||||
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
|
||||
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
|
||||
added_modes:
|
||||
|
||||
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||
# (contrary to the memories, which are loaded on demand).
|
||||
initial_prompt: ""
|
||||
|
||||
@@ -37,13 +37,15 @@ gui_log_window: False
|
||||
# Further information: https://oraios.github.io/serena/02-usage/060_dashboard.html
|
||||
web_dashboard: True
|
||||
|
||||
# whether to open the Dashboard window/browser tab when Serena starts (provided that web_dashboard is enabled).
|
||||
# If set to false, you can still open the dashboard manually by clicking on the Serena icon in your system
|
||||
# tray on Windows and macOS. On Linux, there is no system tray support, so you can only open the dashboard by
|
||||
# a) telling the LLM to "open the dashboard" (provided that the open_dashboard tool is enabled) or by
|
||||
# b) manually navigating to http://localhost:24282/dashboard/ in your web browser (actual port
|
||||
# may be higher if you have multiple instances running; try ports 24283, 24284, etc.)
|
||||
# See also: https://oraios.github.io/serena/02-usage/060_dashboard.html
|
||||
# whether to open the Dashboard window/browser tab when Serena starts (provided that `web_dashboard` is enabled).
|
||||
# If set to false, you can still open the dashboard manually:
|
||||
# * When using an interface that supports a tray icon (see setting `web_dashboard_interface`),
|
||||
# you can conveniently open the dashboard from the system tray.
|
||||
# * When using the `browser` interface (no tray icon), so you can only open the dashboard by
|
||||
# a) telling the LLM to "open the dashboard" (provided that the open_dashboard tool is enabled) or by
|
||||
# b) manually navigating to http://localhost:24282/dashboard/ in your web browser (actual port
|
||||
# may be higher if you have multiple instances running; try ports 24283, 24284, etc.)
|
||||
# Further information: https://oraios.github.io/serena/02-usage/060_dashboard.html
|
||||
web_dashboard_open_on_launch: True
|
||||
|
||||
# defines the interface (application mode) used for the web dashboard (if enabled).
|
||||
@@ -59,6 +61,7 @@ web_dashboard_open_on_launch: True
|
||||
# opening the dashboard in browser tabs when selected from the tray menu.
|
||||
# This is EXPERIMENTAL. It is tested on Windows only. We will establish macOS support, but it is yet untested.
|
||||
# On Linux, this cannot be universally supported, but it may work in some desktop environments.
|
||||
# See https://oraios.github.io/serena/02-usage/060_dashboard.html
|
||||
web_dashboard_interface:
|
||||
|
||||
# the address the web dashboard will listen on (bind address).
|
||||
@@ -112,19 +115,23 @@ included_optional_tools: []
|
||||
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
|
||||
fixed_tools: []
|
||||
|
||||
# list of mode names to that are always to be included in the set of active modes
|
||||
# The full set of modes to be activated is base_modes + default_modes.
|
||||
# If this is undefined, no base modes are included.
|
||||
# The project configuration (project.yml) may override this setting.
|
||||
# list of mode names to that are always to be included in the set of active modes.
|
||||
# The full set of modes to be activated is base_modes + default_modes + added_modes,
|
||||
# where added_modes can be defined by projects/CLI parameters.
|
||||
# If this is undefined/empty, no base modes are included.
|
||||
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
|
||||
base_modes:
|
||||
|
||||
# list of mode names that are to be activated by default.
|
||||
# The full set of modes to be activated is base_modes + default_modes.
|
||||
# These modes can be overridden by the project configuration (project.yml) or through the CLI (--mode).
|
||||
default_modes:
|
||||
- interactive
|
||||
- editing
|
||||
|
||||
# list of mode names that are to be activated by default (but can be overridden by projects/CLI params).
|
||||
# The full set of modes to be activated is base_modes + default_modes + added_modes,
|
||||
# where added_modes are defined by projects/CLI parameters.
|
||||
# If this is undefined/empty, no default modes are defined.
|
||||
# These modes can be overridden by the project configuration (project.yml) or through the CLI (--mode).
|
||||
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
|
||||
default_modes:
|
||||
|
||||
# Used as default for tools where the apply method has a default maximal answer length.
|
||||
# Even though the value of the max_answer_chars can be changed when calling the tool, it may make sense to adjust this default
|
||||
# through the global configuration.
|
||||
|
||||
@@ -23,7 +23,7 @@ from solidlsp.ls_types import SymbolKind
|
||||
|
||||
|
||||
class RestartLanguageServerTool(Tool, ToolMarkerOptional):
|
||||
"""Restarts the language server, may be necessary when edits not through Serena happen."""
|
||||
"""Restarts the language server(s)."""
|
||||
|
||||
def apply(self) -> str:
|
||||
"""Use this tool only on explicit user request or after confirmation.
|
||||
|
||||
@@ -173,6 +173,9 @@ class GitignoreParser:
|
||||
except PermissionError as ex:
|
||||
log.debug(f"Skipping entry due to permission error: {entry.path}", exc_info=ex)
|
||||
continue
|
||||
except FileNotFoundError as ex:
|
||||
log.debug(f"Skipping entry due to file not found error (possibly a broken link): {entry.path}", exc_info=ex)
|
||||
continue
|
||||
|
||||
while queue:
|
||||
next_abs_path = queue.pop(0)
|
||||
|
||||
@@ -224,6 +224,15 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
||||
def _create_launch_command(self, core_path: str) -> list[str]:
|
||||
return [core_path, "--stdio"]
|
||||
|
||||
def _get_language_id_for_file(self, relative_file_path: str) -> str:
|
||||
# JSX is parsed as TS without this, which silently truncates symbol
|
||||
# ranges at the first multi-line JSX expression.
|
||||
if relative_file_path.endswith(".tsx"):
|
||||
return "typescriptreact"
|
||||
if relative_file_path.endswith(".jsx"):
|
||||
return "javascriptreact"
|
||||
return self.language_id
|
||||
|
||||
def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
Returns the initialize params for the TypeScript Language Server.
|
||||
|
||||
+10
-4
@@ -487,10 +487,14 @@ class SolidLanguageServer(ABC):
|
||||
|
||||
:param config: the global SolidLSP configuration.
|
||||
:param repository_root_path: the root path of the repository.
|
||||
:param process_launch_info: (DEPRECATED - implement _create_dependency_provider instead)
|
||||
:param process_launch_info: (DEPRECATED: pass None and implement _create_dependency_provider instead)
|
||||
the command used to start the actual language server.
|
||||
The command must pass appropriate flags to the binary, so that it runs in the stdio mode,
|
||||
as opposed to HTTP, TCP modes supported by some language servers.
|
||||
:param language_id: The language identifier which will be passed to the language server in the `textDocument/didOpen`
|
||||
notification by default.
|
||||
If the language server uses multiple language identifiers, it must override the method `get_language_id_for_file`
|
||||
to provide the appropriate identifier for each type of file.
|
||||
:param cache_version_raw_document_symbols: the version, for caching, of the raw document symbols coming
|
||||
from this specific language server. This should be incremented by subclasses calling this constructor
|
||||
whenever the format of the raw document symbols changes (typically because the language server
|
||||
@@ -1116,10 +1120,12 @@ class SolidLanguageServer(ABC):
|
||||
pass
|
||||
|
||||
def _get_language_id_for_file(self, relative_file_path: str) -> str:
|
||||
"""Return the language ID for a file.
|
||||
"""
|
||||
Determines the language identifier to pass to the language server for the given file,
|
||||
particularly `textDocument/didOpen` requests.
|
||||
|
||||
Override in subclasses to return file-specific language IDs.
|
||||
Default implementation returns self.language_id.
|
||||
Override this method in subclasses to return file-specific language identifiers.
|
||||
The default implementation returns the main identifier passed at construction (self.language_id).
|
||||
"""
|
||||
return self.language_id
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Regression fixture: mirrors the real-world pattern that caused
|
||||
// languageId=typescript (instead of typescriptreact) to truncate symbol
|
||||
// ranges at the first multi-line JSX expression.
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
type SectionProps = { title: string; emphasised?: boolean }
|
||||
|
||||
function Section({ title, emphasised }: SectionProps) {
|
||||
return emphasised ? (
|
||||
<h1 style={{ color: "red" }}>{title}</h1>
|
||||
) : (
|
||||
<h2 style={{ color: "gray" }}>{title}</h2>
|
||||
)
|
||||
}
|
||||
|
||||
export function JsxComponent({ heading, items }: { heading: string; items: string[] }) {
|
||||
const renderHeader = (title: string) =>
|
||||
title.length > 10 ? (
|
||||
<Section title={title} emphasised />
|
||||
) : (
|
||||
<Section title={title} />
|
||||
)
|
||||
|
||||
// The truncation bug used to cut JsxComponent's range right around here,
|
||||
// hiding everything below from find_symbol. Keep this comment so the
|
||||
// regression test can assert the function body extends past it.
|
||||
const renderItem = (item: string, idx: number) => (
|
||||
<li key={idx} style={{ marginBottom: 4 }}>
|
||||
{item}
|
||||
</li>
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{renderHeader(heading)}
|
||||
<ul>{items.map(renderItem)}</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function trailingHelper(): string {
|
||||
return "this symbol is invisible to find_symbol when JsxComponent's range is truncated"
|
||||
}
|
||||
@@ -5,7 +5,8 @@
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true
|
||||
"skipLibCheck": true,
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
"include": ["**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
@@ -61,6 +61,37 @@ class TestTypescriptLanguageServer:
|
||||
for symbol in implementing_symbols
|
||||
), f"Expected ConsoleGreeter.formatGreeting symbol, got: {implementing_symbols}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.TYPESCRIPT], indirect=True)
|
||||
def test_tsx_symbol_range_not_truncated_by_jsx(self, language_server: SolidLanguageServer) -> None:
|
||||
# Regression: when the language id is sent as "typescript" instead of
|
||||
# "typescriptreact" for .tsx files, tsserver parses JSX as syntax
|
||||
# errors and recovers by truncating the enclosing symbol's range at
|
||||
# the first multi-line JSX expression. find_symbol then returns a
|
||||
# body that ends mid-component and hides everything below.
|
||||
file_path = "jsx_component.tsx"
|
||||
roots = language_server.request_document_symbols(file_path).root_symbols
|
||||
|
||||
jsx_component = next((s for s in roots if s.get("name") == "JsxComponent"), None)
|
||||
assert jsx_component is not None, "JsxComponent not found at root level of jsx_component.tsx"
|
||||
|
||||
end_line = jsx_component["location"]["range"]["end"]["line"]
|
||||
# JsxComponent's body extends to line 38 (0-based 37) in the fixture;
|
||||
# the truncation bug cut it at the first multi-line JSX (~line 21).
|
||||
# Use a generous lower bound so the test survives small fixture edits
|
||||
# that don't affect the regression behaviour we care about.
|
||||
assert end_line >= 30, (
|
||||
f"JsxComponent symbol range truncated at line {end_line + 1} (1-based); "
|
||||
f"expected end at or past line 31 (1-based). "
|
||||
f"This indicates the .tsx file was opened with the wrong languageId."
|
||||
)
|
||||
|
||||
# The trailing helper must be visible as a top-level symbol — it lives
|
||||
# past the truncation point and disappears entirely when the bug is
|
||||
# active because tsserver stops emitting symbols after the parse error.
|
||||
assert any(s.get("name") == "trailingHelper" for s in roots), (
|
||||
"trailingHelper missing from jsx_component.tsx root symbols; tsserver likely stopped parsing at the first JSX expression."
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.TYPESCRIPT], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
|
||||
Reference in new issue
Block a user