From e783bdbc1b36cc4e877d75bf936313619f5a723d Mon Sep 17 00:00:00 2001 From: Marius Ailinca Date: Mon, 23 Feb 2026 00:43:53 +0200 Subject: [PATCH 1/3] feat: support per-project language_backend override MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Determine the effective language backend at startup: - If a startup project has a per-project override, use it - Otherwise use the global config Post-init activation of a project with a different backend returns an error with workarounds (startup activation or separate MCP server per backend). No dynamic tool swapping — the exposed toolset is fixed for the session. Co-Authored-By: Claude Opus 4.6 --- src/serena/agent.py | 37 ++++- src/serena/config/serena_config.py | 10 ++ src/serena/dashboard.py | 4 +- src/serena/resources/project.template.yml | 7 + test/serena/config/test_serena_config.py | 192 +++++++++++++++++++++- 5 files changed, 244 insertions(+), 6 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index 1997b7de..2876b751 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -17,7 +17,7 @@ from interprompt.jinja_template import JinjaTemplate from serena import serena_version from serena.analytics import RegisteredTokenCountEstimator, ToolUsageStats from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode -from serena.config.serena_config import LanguageBackend, ModeSelectionDefinition, SerenaConfig, ToolInclusionDefinition +from serena.config.serena_config import LanguageBackend, ModeSelectionDefinition, ProjectConfig, SerenaConfig, ToolInclusionDefinition from serena.dashboard import SerenaDashboardAPI from serena.ls_manager import LanguageServerManager from serena.project import Project @@ -318,7 +318,16 @@ class SerenaAgent: tool_inclusion_definitions.append(self._context) if self._context.single_project: tool_inclusion_definitions.extend(self._single_project_context_tool_inclusion_definitions(project)) - if self.serena_config.language_backend == LanguageBackend.JETBRAINS: + # Determine the effective language backend for this session. + # If a startup project is provided and has a per-project override, use it; otherwise use the global config. + self._effective_language_backend = self.serena_config.language_backend + if project is not None: + startup_project_config = self._find_project_config(project) + if startup_project_config is not None and startup_project_config.language_backend is not None: + self._effective_language_backend = startup_project_config.language_backend + log.info(f"Using per-project language backend override: {self._effective_language_backend.name}") + log.info(f"Effective language backend for this session: {self._effective_language_backend.name}") + if self._effective_language_backend == LanguageBackend.JETBRAINS: tool_inclusion_definitions.append(SerenaAgentMode.from_name_internal("jetbrains")) self._base_tool_set = ToolSet.default().apply(*tool_inclusion_definitions) self._exposed_tools = AvailableTools([t for t in self._all_tools.values() if self._base_tool_set.includes_name(t.get_name())]) @@ -615,10 +624,28 @@ class SerenaAgent: """ :return: whether this agent uses language server-based code analysis """ - return self.serena_config.language_backend == LanguageBackend.LSP + return self._effective_language_backend == LanguageBackend.LSP + + def _find_project_config(self, project_path_or_name: str) -> ProjectConfig | None: + """Find the ProjectConfig for the given project path or name, or None if not found.""" + registered = self.serena_config.get_registered_project(project_path_or_name) + if registered is not None: + return registered.project_config + return None def _activate_project(self, project: Project, update_modes_and_tools: bool = True) -> None: log.info(f"Activating {project.project_name} at {project.project_root}") + + # Check if the project requires a different language backend than the one initialized at startup + project_backend = project.project_config.language_backend + if project_backend is not None and project_backend != self._effective_language_backend: + raise ValueError( + f"Cannot activate project '{project.project_name}': it requires the {project_backend.value} backend, " + f"but this session was initialized with {self._effective_language_backend.value}. " + f"Workarounds: (1) Use project activation at startup via the --project flag, " + f"(2) Configure one MCP server per backend in your client." + ) + self._active_project = project if update_modes_and_tools: @@ -696,6 +723,10 @@ class SerenaAgent: result_str += f"Active project: {self._active_project.project_name}\n" else: result_str += "No active project\n" + result_str += f"Language backend: {self._effective_language_backend.value}" + if self._active_project and self._active_project.project_config.language_backend is not None: + result_str += " (project override)" + result_str += f" (global default: {self.serena_config.language_backend.value})\n" result_str += "Available projects:\n" + "\n".join(list(self.serena_config.project_names)) + "\n" result_str += f"Active context: {self._context.name}\n" diff --git a/src/serena/config/serena_config.py b/src/serena/config/serena_config.py index 78dec2e3..7d615a32 100644 --- a/src/serena/config/serena_config.py +++ b/src/serena/config/serena_config.py @@ -181,6 +181,11 @@ class ProjectConfig(SharedConfig): ignore_all_files_in_gitignore: bool = True initial_prompt: str = "" encoding: str = DEFAULT_SOURCE_FILE_ENCODING + language_backend: LanguageBackend | None = None + """ + The language backend to use for this project. + If None, the global setting from SerenaConfig is used. + """ SERENA_DEFAULT_PROJECT_FILE = "project.yml" FIELDS_WITHOUT_DEFAULTS = {"project_name", "languages"} @@ -344,6 +349,9 @@ class ProjectConfig(SharedConfig): if symbol_info_budget < 0: raise ValueError(f"symbol_info_budget cannot be negative, got: {symbol_info_budget}") + language_backend_value = data.get("language_backend") + language_backend = LanguageBackend.from_str(language_backend_value) if language_backend_value else None + return cls( project_name=data["project_name"], languages=languages, @@ -355,6 +363,7 @@ class ProjectConfig(SharedConfig): ignore_all_files_in_gitignore=data["ignore_all_files_in_gitignore"], initial_prompt=data["initial_prompt"], encoding=data["encoding"], + language_backend=language_backend, base_modes=data["base_modes"], default_modes=data["default_modes"], symbol_info_budget=symbol_info_budget, @@ -366,6 +375,7 @@ class ProjectConfig(SharedConfig): """ d = dataclasses.asdict(self) d["languages"] = [lang.value for lang in self.languages] + d["language_backend"] = self.language_backend.value if self.language_backend is not None else None return d @classmethod diff --git a/src/serena/dashboard.py b/src/serena/dashboard.py index db7ab4a7..a5c91a04 100644 --- a/src/serena/dashboard.py +++ b/src/serena/dashboard.py @@ -10,7 +10,7 @@ from pydantic import BaseModel from sensai.util import logging from serena.analytics import ToolUsageStats -from serena.config.serena_config import LanguageBackend, SerenaConfig, SerenaPaths +from serena.config.serena_config import SerenaConfig, SerenaPaths from serena.constants import SERENA_DASHBOARD_DIR from serena.task_executor import TaskExecutor from serena.util.logging import MemoryLogHandler @@ -507,7 +507,7 @@ class SerenaDashboardAPI: available_modes=available_modes, available_contexts=available_contexts, available_memories=available_memories, - jetbrains_mode=self._agent.serena_config.language_backend == LanguageBackend.JETBRAINS, + jetbrains_mode=not self._agent.is_using_language_server(), languages=languages, encoding=encoding, current_client=Tool.get_last_tool_call_client_str(), diff --git a/src/serena/resources/project.template.yml b/src/serena/resources/project.template.yml index 1f7ca87b..c95452b7 100644 --- a/src/serena/resources/project.template.yml +++ b/src/serena/resources/project.template.yml @@ -31,6 +31,13 @@ languages: ["python"] # For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings encoding: "utf-8" +# The language backend to use for this project. +# If not set, the global setting from serena_config.yml is used. +# Valid values: LSP, JetBrains +# Note: the backend is fixed at startup. If a project with a different backend +# is activated post-init, an error will be returned. +language_backend: + # whether to use project's .gitignore files to ignore files ignore_all_files_in_gitignore: true diff --git a/test/serena/config/test_serena_config.py b/test/serena/config/test_serena_config.py index 93abd5a6..ea228815 100644 --- a/test/serena/config/test_serena_config.py +++ b/test/serena/config/test_serena_config.py @@ -1,11 +1,14 @@ +import logging import shutil import tempfile from pathlib import Path import pytest -from serena.config.serena_config import ProjectConfig +from serena.agent import SerenaAgent +from serena.config.serena_config import LanguageBackend, ProjectConfig, RegisteredProject, SerenaConfig from serena.constants import PROJECT_TEMPLATE_FILE +from serena.project import Project from solidlsp.ls_config import Language @@ -124,3 +127,190 @@ class TestProjectConfig: def test_template_is_complete(self): _, is_complete = ProjectConfig._load_yaml(PROJECT_TEMPLATE_FILE) assert is_complete, "Project template YAML is incomplete; all fields must be present (with descriptions)." + + +class TestProjectConfigLanguageBackend: + """Tests for the per-project language_backend field.""" + + def test_language_backend_defaults_to_none(self): + config = ProjectConfig( + project_name="test", + languages=[Language.PYTHON], + ) + assert config.language_backend is None + + def test_language_backend_can_be_set(self): + config = ProjectConfig( + project_name="test", + languages=[Language.PYTHON], + language_backend=LanguageBackend.JETBRAINS, + ) + assert config.language_backend == LanguageBackend.JETBRAINS + + def test_language_backend_roundtrips_through_yaml(self): + config = ProjectConfig( + project_name="test", + languages=[Language.PYTHON], + language_backend=LanguageBackend.JETBRAINS, + ) + d = config._to_yaml_dict() + assert d["language_backend"] == "JetBrains" + + def test_language_backend_none_roundtrips_through_yaml(self): + config = ProjectConfig( + project_name="test", + languages=[Language.PYTHON], + ) + d = config._to_yaml_dict() + assert d["language_backend"] is None + + def test_language_backend_parsed_from_dict(self): + """Test that _from_dict parses language_backend correctly.""" + template_path = PROJECT_TEMPLATE_FILE + data, _ = ProjectConfig._load_yaml(template_path) + data["project_name"] = "test" + data["languages"] = ["python"] + data["language_backend"] = "JetBrains" + config = ProjectConfig._from_dict(data) + assert config.language_backend == LanguageBackend.JETBRAINS + + def test_language_backend_none_when_missing_from_dict(self): + """Test that _from_dict handles missing language_backend gracefully.""" + template_path = PROJECT_TEMPLATE_FILE + data, _ = ProjectConfig._load_yaml(template_path) + data["project_name"] = "test" + data["languages"] = ["python"] + data.pop("language_backend", None) + config = ProjectConfig._from_dict(data) + assert config.language_backend is None + + +def _make_config_with_project( + project_name: str, + language_backend: LanguageBackend | None = None, + global_backend: LanguageBackend = LanguageBackend.LSP, +) -> tuple[SerenaConfig, str]: + """Create a SerenaConfig with a single registered project and return (config, project_name).""" + project = Project( + project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "python" / "test_repo"), + project_config=ProjectConfig( + project_name=project_name, + languages=[Language.PYTHON], + language_backend=language_backend, + ), + ) + config = SerenaConfig( + gui_log_window=False, + web_dashboard=False, + log_level=logging.ERROR, + language_backend=global_backend, + ) + config.projects = [RegisteredProject.from_project_instance(project)] + return config, project_name + + +class TestEffectiveLanguageBackend: + """Tests for per-project language_backend override logic in SerenaAgent.""" + + def test_default_backend_is_global(self): + """When no project override, effective backend matches global config.""" + config, name = _make_config_with_project("test_proj", language_backend=None, global_backend=LanguageBackend.LSP) + agent = SerenaAgent(project=name, serena_config=config) + try: + assert agent._effective_language_backend == LanguageBackend.LSP + assert agent.is_using_language_server() is True + finally: + agent.shutdown(timeout=5) + + def test_project_overrides_global_backend(self): + """When startup project has language_backend set, it overrides the global.""" + config, name = _make_config_with_project( + "test_jetbrains", language_backend=LanguageBackend.JETBRAINS, global_backend=LanguageBackend.LSP + ) + agent = SerenaAgent(project=name, serena_config=config) + try: + assert agent._effective_language_backend == LanguageBackend.JETBRAINS + assert agent.is_using_language_server() is False + finally: + agent.shutdown(timeout=5) + + def test_no_project_uses_global_backend(self): + """When no startup project is provided, effective backend is the global one.""" + config = SerenaConfig( + gui_log_window=False, + web_dashboard=False, + log_level=logging.ERROR, + language_backend=LanguageBackend.LSP, + ) + agent = SerenaAgent(project=None, serena_config=config) + try: + assert agent._effective_language_backend == LanguageBackend.LSP + finally: + agent.shutdown(timeout=5) + + def test_activate_project_rejects_backend_mismatch(self): + """Post-init activation of a project with mismatched backend raises ValueError.""" + # Start with LSP backend + config, name = _make_config_with_project("lsp_proj", language_backend=None, global_backend=LanguageBackend.LSP) + + # Add a second project that requires JetBrains + jb_project = Project( + project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "python" / "test_repo"), + project_config=ProjectConfig( + project_name="jb_proj", + languages=[Language.PYTHON], + language_backend=LanguageBackend.JETBRAINS, + ), + ) + config.projects.append(RegisteredProject.from_project_instance(jb_project)) + + agent = SerenaAgent(project=name, serena_config=config) + try: + with pytest.raises(ValueError, match="Cannot activate project"): + agent.activate_project_from_path_or_name("jb_proj") + finally: + agent.shutdown(timeout=5) + + def test_activate_project_allows_matching_backend(self): + """Post-init activation of a project with matching backend succeeds.""" + config, name = _make_config_with_project("lsp_proj", language_backend=None, global_backend=LanguageBackend.LSP) + + # Add a second project that also uses LSP + lsp_project2 = Project( + project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "python" / "test_repo"), + project_config=ProjectConfig( + project_name="lsp_proj2", + languages=[Language.PYTHON], + language_backend=LanguageBackend.LSP, + ), + ) + config.projects.append(RegisteredProject.from_project_instance(lsp_project2)) + + agent = SerenaAgent(project=name, serena_config=config) + try: + # Should not raise + agent.activate_project_from_path_or_name("lsp_proj2") + finally: + agent.shutdown(timeout=5) + + def test_activate_project_allows_none_backend(self): + """Post-init activation of a project with no backend override succeeds.""" + config, name = _make_config_with_project("lsp_proj", language_backend=None, global_backend=LanguageBackend.LSP) + + # Add a second project with no backend override + proj2 = Project( + project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "python" / "test_repo"), + project_config=ProjectConfig( + project_name="proj2", + languages=[Language.PYTHON], + language_backend=None, + ), + ) + config.projects.append(RegisteredProject.from_project_instance(proj2)) + + agent = SerenaAgent(project=name, serena_config=config) + try: + # Should not raise — None means "inherit session backend" + agent.activate_project_from_path_or_name("proj2") + finally: + agent.shutdown(timeout=5) From e9ccd3360f2b4a41ac9bf51cf28c0a1a391390dc Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Mon, 23 Feb 2026 13:45:19 +0100 Subject: [PATCH 2/3] Minor renaming, removed usage of private attrs in tests --- src/serena/agent.py | 28 ++++++++++++++++-------- test/serena/config/test_serena_config.py | 6 ++--- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index 2876b751..7f0bd652 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -318,16 +318,23 @@ class SerenaAgent: tool_inclusion_definitions.append(self._context) if self._context.single_project: tool_inclusion_definitions.extend(self._single_project_context_tool_inclusion_definitions(project)) + # Determine the effective language backend for this session. # If a startup project is provided and has a per-project override, use it; otherwise use the global config. - self._effective_language_backend = self.serena_config.language_backend + # Since we don't want to change the toolset after startup, the language backend cannot be changed within a running serena session + self._language_backend = self.serena_config.language_backend + _backend_from_project_config = False if project is not None: startup_project_config = self._find_project_config(project) if startup_project_config is not None and startup_project_config.language_backend is not None: - self._effective_language_backend = startup_project_config.language_backend - log.info(f"Using per-project language backend override: {self._effective_language_backend.name}") - log.info(f"Effective language backend for this session: {self._effective_language_backend.name}") - if self._effective_language_backend == LanguageBackend.JETBRAINS: + self._language_backend = startup_project_config.language_backend + _backend_from_project_config = True + if _backend_from_project_config: + log.info(f"Using language backend as configured in project.yml: {self._language_backend.name}") + else: + log.info(f"Using language backend from global serena config: {self._language_backend.name}") + + if self._language_backend == LanguageBackend.JETBRAINS: tool_inclusion_definitions.append(SerenaAgentMode.from_name_internal("jetbrains")) self._base_tool_set = ToolSet.default().apply(*tool_inclusion_definitions) self._exposed_tools = AvailableTools([t for t in self._all_tools.values() if self._base_tool_set.includes_name(t.get_name())]) @@ -374,6 +381,9 @@ class SerenaAgent: if self._gui_log_viewer is not None: self._gui_log_viewer.set_dashboard_url(dashboard_url) + def get_language_backend(self) -> LanguageBackend: + return self._language_backend + def get_current_tasks(self) -> list[TaskExecutor.TaskInfo]: """ Gets the list of tasks currently running or queued for execution. @@ -624,7 +634,7 @@ class SerenaAgent: """ :return: whether this agent uses language server-based code analysis """ - return self._effective_language_backend == LanguageBackend.LSP + return self._language_backend == LanguageBackend.LSP def _find_project_config(self, project_path_or_name: str) -> ProjectConfig | None: """Find the ProjectConfig for the given project path or name, or None if not found.""" @@ -638,10 +648,10 @@ class SerenaAgent: # Check if the project requires a different language backend than the one initialized at startup project_backend = project.project_config.language_backend - if project_backend is not None and project_backend != self._effective_language_backend: + if project_backend is not None and project_backend != self._language_backend: raise ValueError( f"Cannot activate project '{project.project_name}': it requires the {project_backend.value} backend, " - f"but this session was initialized with {self._effective_language_backend.value}. " + f"but this session was initialized with {self._language_backend.value}. " f"Workarounds: (1) Use project activation at startup via the --project flag, " f"(2) Configure one MCP server per backend in your client." ) @@ -723,7 +733,7 @@ class SerenaAgent: result_str += f"Active project: {self._active_project.project_name}\n" else: result_str += "No active project\n" - result_str += f"Language backend: {self._effective_language_backend.value}" + result_str += f"Language backend: {self._language_backend.value}" if self._active_project and self._active_project.project_config.language_backend is not None: result_str += " (project override)" result_str += f" (global default: {self.serena_config.language_backend.value})\n" diff --git a/test/serena/config/test_serena_config.py b/test/serena/config/test_serena_config.py index ea228815..e4515a0d 100644 --- a/test/serena/config/test_serena_config.py +++ b/test/serena/config/test_serena_config.py @@ -217,7 +217,7 @@ class TestEffectiveLanguageBackend: config, name = _make_config_with_project("test_proj", language_backend=None, global_backend=LanguageBackend.LSP) agent = SerenaAgent(project=name, serena_config=config) try: - assert agent._effective_language_backend == LanguageBackend.LSP + assert agent.get_language_backend() == LanguageBackend.LSP assert agent.is_using_language_server() is True finally: agent.shutdown(timeout=5) @@ -229,7 +229,7 @@ class TestEffectiveLanguageBackend: ) agent = SerenaAgent(project=name, serena_config=config) try: - assert agent._effective_language_backend == LanguageBackend.JETBRAINS + assert agent.get_language_backend() == LanguageBackend.JETBRAINS assert agent.is_using_language_server() is False finally: agent.shutdown(timeout=5) @@ -244,7 +244,7 @@ class TestEffectiveLanguageBackend: ) agent = SerenaAgent(project=None, serena_config=config) try: - assert agent._effective_language_backend == LanguageBackend.LSP + assert agent.get_language_backend() == LanguageBackend.LSP finally: agent.shutdown(timeout=5) From 6decf2e8d1afaba839cecb190d7568c93035c300 Mon Sep 17 00:00:00 2001 From: Marius Ailinca Date: Tue, 24 Feb 2026 12:53:08 +0200 Subject: [PATCH 3/3] Docs: document per-project language_backend override; move field to SharedConfig - Add "Per-Project Configuration" section to JetBrains plugin docs with cross-reference anchor for linking from other pages - Add language_backend to project configuration feature list in workflow docs - Add cross-reference from global config docs to per-project override - Move language_backend field from ProjectConfig to SharedConfig, following the same pattern as symbol_info_budget (review feedback) Co-Authored-By: Claude Opus 4.6 --- docs/02-usage/025_jetbrains_plugin.md | 25 ++++++++++++++-- docs/02-usage/040_workflow.md | 5 ++-- docs/02-usage/050_configuration.md | 3 +- src/serena/config/serena_config.py | 42 ++++++++++++--------------- 4 files changed, 47 insertions(+), 28 deletions(-) diff --git a/docs/02-usage/025_jetbrains_plugin.md b/docs/02-usage/025_jetbrains_plugin.md index 4da61572..32c69591 100644 --- a/docs/02-usage/025_jetbrains_plugin.md +++ b/docs/02-usage/025_jetbrains_plugin.md @@ -48,10 +48,31 @@ language_backend: JetBrains *Note*: you can also use the button `Edit Global Serena Config` in the Serena MCP dashboard to open the config file in your default editor. **Per-Instance Configuration**. -The configuration setting in the global config file can be overridden on a -per-instance basis by providing the arguments `--language-backend JetBrains` when +The configuration setting in the global config file can be overridden on a +per-instance basis by providing the arguments `--language-backend JetBrains` when launching the Serena MCP server. +(per-project-language-backend)= +**Per-Project Configuration**. +You can also set the language backend on a per-project basis in the project's +`.serena/project.yml` file: + +```yaml +language_backend: JetBrains +``` + +If set, this overrides the global `language_backend` setting for the session when the project is +activated at startup (via the `--project` flag). + +:::{important} +The language backend is determined once at startup and cannot be changed during a running session. +If a project with a different backend is activated after startup, Serena will return an error. + +If you need to work with projects that use different backends, you can either: +1. Use the `--project` flag to activate the project at startup, which will use its configured backend. +2. Configure separate MCP server instances (one per backend) in your client. +::: + **Verifying the Setup**. You can verify that Serena is using the JetBrains plugin by either checking the dashboard, where you will see `Languages: diff --git a/docs/02-usage/040_workflow.md b/docs/02-usage/040_workflow.md index 8f90fe68..c5153401 100644 --- a/docs/02-usage/040_workflow.md +++ b/docs/02-usage/040_workflow.md @@ -46,12 +46,13 @@ After creation, you can adjust the project settings in the generated `.serena/pr within the project directory. The file allows you to configure ... - * the set of programming languages for which language servers are spawned (not relevant when using the JetBrains plugin) + * the set of programming languages for which language servers are spawned (not relevant when using the JetBrains plugin) Note that you can dynamically add/remove language servers while Serena is running via the [Dashboard](060_dashboard). + * the [language backend](per-project-language-backend) to use for this project (overriding the global setting) * the encoding used in source files * ignore rules * write access - * an initial prompt that shall be passed to the LLM whenever the project is activated + * an initial prompt that shall be passed to the LLM whenever the project is activated * the name by which you want to refer to the project (relevant when telling the LLM to dynamically activate the project) * the set of tools and modes to use by default diff --git a/docs/02-usage/050_configuration.md b/docs/02-usage/050_configuration.md index 85bad8d4..336e9679 100644 --- a/docs/02-usage/050_configuration.md +++ b/docs/02-usage/050_configuration.md @@ -23,7 +23,8 @@ The global configuration file allows you to change general settings and defaults ### Settings Some of the configurable settings include: - * the language backend to use by default (i.e., the JetBrains plugin or language servers) + * the language backend to use by default (i.e., the JetBrains plugin or language servers); + this can also be [overridden per project](per-project-language-backend) * UI settings affecting the [Serena Dashboard and GUI tool](060_dashboard.md) * the set of tools to enable/disable by default * the set of modes to use by default diff --git a/src/serena/config/serena_config.py b/src/serena/config/serena_config.py index 7d615a32..0fab8fd4 100644 --- a/src/serena/config/serena_config.py +++ b/src/serena/config/serena_config.py @@ -134,24 +134,6 @@ class ModeSelectionDefinition: default_modes: Sequence[str] | None = None -@dataclass -class SharedConfig(ModeSelectionDefinition, 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. - """ - - symbol_info_budget: float | None = None - - -class SerenaConfigError(Exception): - pass - - -def get_serena_managed_in_project_dir(project_root: str | Path) -> str: - return os.path.join(project_root, SERENA_MANAGED_DIR_NAME) - - class LanguageBackend(Enum): LSP = "LSP" """ @@ -172,6 +154,25 @@ class LanguageBackend(Enum): raise ValueError(f"Unknown language backend '{backend_str}': valid values are {[b.value for b in LanguageBackend]}") +@dataclass +class SharedConfig(ModeSelectionDefinition, 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. + """ + + symbol_info_budget: float | None = None + language_backend: LanguageBackend | None = None + + +class SerenaConfigError(Exception): + pass + + +def get_serena_managed_in_project_dir(project_root: str | Path) -> str: + return os.path.join(project_root, SERENA_MANAGED_DIR_NAME) + + @dataclass(kw_only=True) class ProjectConfig(SharedConfig): project_name: str @@ -181,11 +182,6 @@ class ProjectConfig(SharedConfig): ignore_all_files_in_gitignore: bool = True initial_prompt: str = "" encoding: str = DEFAULT_SOURCE_FILE_ENCODING - language_backend: LanguageBackend | None = None - """ - The language backend to use for this project. - If None, the global setting from SerenaConfig is used. - """ SERENA_DEFAULT_PROJECT_FILE = "project.yml" FIELDS_WITHOUT_DEFAULTS = {"project_name", "languages"}