From 9781710f4e634614a872994683e78c9b35877a6e Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sun, 23 Nov 2025 22:29:53 +0100 Subject: [PATCH 01/10] Sync mux commit c5b59a6cb87a093327b3bc2bbfa2a485a3b18d1f Author: Dominik Jain Date: Sun Nov 23 18:53:06 2025 +0100 ToolRegistry: Allow valid tool packages to be modified src-serena/serena/tools/tools_base.py commit 9b4299d4f3e12fe67919e304d9e33c527ee0f345 Author: Dominik Jain Date: Sun Nov 23 17:47:39 2025 +0100 ToolInclusionDefinition: Add option to specify a fixed tool set src-serena/serena/agent.py src-serena/serena/config/serena_config.py --- src/serena/agent.py | 49 ++++++++++++++++++------------ src/serena/config/serena_config.py | 30 ++++++++++++++++-- src/serena/tools/tools_base.py | 5 ++- 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index dcbe2dbf..70d5bb73 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -106,26 +106,35 @@ class ToolSet: registry = ToolRegistry() tool_names = set(self._tool_names) for definition in tool_inclusion_definitions: - included_tools = [] - excluded_tools = [] - for included_tool in definition.included_optional_tools: - included_tool = get_updated_tool_name(included_tool) - if not registry.is_valid_tool_name(included_tool): - raise ValueError(f"Invalid tool name '{included_tool}' provided for inclusion") - if included_tool not in tool_names: - tool_names.add(included_tool) - included_tools.append(included_tool) - for excluded_tool in definition.excluded_tools: - excluded_tool = get_updated_tool_name(excluded_tool) - if not registry.is_valid_tool_name(excluded_tool): - raise ValueError(f"Invalid tool name '{excluded_tool}' provided for exclusion") - if excluded_tool in tool_names: - tool_names.remove(excluded_tool) - excluded_tools.append(excluded_tool) - if included_tools: - log.info(f"{definition} included {len(included_tools)} tools: {', '.join(included_tools)}") - if excluded_tools: - log.info(f"{definition} excluded {len(excluded_tools)} tools: {', '.join(excluded_tools)}") + if definition.is_fixed_tool_set(): + tool_names = set() + for fixed_tool in definition.fixed_tools: + fixed_tool = get_updated_tool_name(fixed_tool) + if not registry.is_valid_tool_name(fixed_tool): + raise ValueError(f"Invalid tool name '{fixed_tool}' provided for fixed tool set") + tool_names.add(fixed_tool) + log.info(f"{definition} defined a fixed tool set with {len(tool_names)} tools: {', '.join(tool_names)}") + else: + included_tools = [] + excluded_tools = [] + for included_tool in definition.included_optional_tools: + included_tool = get_updated_tool_name(included_tool) + if not registry.is_valid_tool_name(included_tool): + raise ValueError(f"Invalid tool name '{included_tool}' provided for inclusion") + if included_tool not in tool_names: + tool_names.add(included_tool) + included_tools.append(included_tool) + for excluded_tool in definition.excluded_tools: + excluded_tool = get_updated_tool_name(excluded_tool) + if not registry.is_valid_tool_name(excluded_tool): + raise ValueError(f"Invalid tool name '{excluded_tool}' provided for exclusion") + if excluded_tool in tool_names: + tool_names.remove(excluded_tool) + excluded_tools.append(excluded_tool) + if included_tools: + log.info(f"{definition} included {len(included_tools)} tools: {', '.join(included_tools)}") + if excluded_tools: + log.info(f"{definition} excluded {len(excluded_tools)} tools: {', '.join(excluded_tools)}") return ToolSet(tool_names) def without_editing_tools(self) -> "ToolSet": diff --git a/src/serena/config/serena_config.py b/src/serena/config/serena_config.py index c5345d48..6cc74f84 100644 --- a/src/serena/config/serena_config.py +++ b/src/serena/config/serena_config.py @@ -5,7 +5,7 @@ The Serena Model Context Protocol (MCP) Server import dataclasses import os import shutil -from collections.abc import Iterable +from collections.abc import Sequence from copy import deepcopy from dataclasses import dataclass, field from datetime import datetime @@ -72,8 +72,32 @@ class SerenaPaths: @dataclass class ToolInclusionDefinition: - excluded_tools: Iterable[str] = () - included_optional_tools: Iterable[str] = () + """ + Defines which tools to include/exclude in Serena's operation. + This can mean either + * defining exclusions/inclusions to apply to an existing set of tools [incremental mode], or + * defining a fixed set of tools to use [fixed mode]. + """ + + excluded_tools: Sequence[str] = () + """ + the names of tools to exclude from use [incremental mode] + """ + included_optional_tools: Sequence[str] = () + """ + the names of optional tools to include [incremental mode] + """ + fixed_tools: Sequence[str] = () + """ + the names of tools to use as a fixed set of tools [fixed mode] + """ + + def is_fixed_tool_set(self) -> bool: + num_fixed = len(self.fixed_tools) + num_incremental = len(self.excluded_tools) + len(self.included_optional_tools) + if num_fixed > 0 and num_incremental > 0: + raise ValueError("Cannot use both fixed_tools and excluded_tools/included_optional_tools at the same time.") + return num_fixed > 0 class SerenaConfigError(Exception): diff --git a/src/serena/tools/tools_base.py b/src/serena/tools/tools_base.py index a79f2911..cb163357 100644 --- a/src/serena/tools/tools_base.py +++ b/src/serena/tools/tools_base.py @@ -350,12 +350,15 @@ class RegisteredTool: tool_name: str +tool_packages = ["serena.tools"] + + @singleton class ToolRegistry: def __init__(self) -> None: self._tool_dict: dict[str, RegisteredTool] = {} for cls in iter_subclasses(Tool): - if not cls.__module__.startswith("serena.tools"): + if not any(cls.__module__.startswith(pkg) for pkg in tool_packages): continue is_optional = issubclass(cls, ToolMarkerOptional) name = cls.get_name_from_cls() From 7e507c11b98df464b89c77a29726db4b86d001e2 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sun, 23 Nov 2025 23:36:45 +0100 Subject: [PATCH 02/10] Sync mux commit 32170df9a77208235818872af48477689c2c8ebe Author: Dominik Jain Date: Sun Nov 23 23:35:28 2025 +0100 Refactor SerenaMCPFactory, removing obsolete subclass src-serena/serena/mcp.py --- src/serena/mcp.py | 72 +++++++++++++++-------------------------------- 1 file changed, 23 insertions(+), 49 deletions(-) diff --git a/src/serena/mcp.py b/src/serena/mcp.py index f8f75ae0..6030e9a0 100644 --- a/src/serena/mcp.py +++ b/src/serena/mcp.py @@ -3,7 +3,6 @@ The Serena Model Context Protocol (MCP) Server """ import sys -from abc import abstractmethod from collections.abc import AsyncIterator, Iterator, Sequence from contextlib import asynccontextmanager from copy import deepcopy @@ -47,15 +46,22 @@ class SerenaMCPRequestContext: class SerenaMCPFactory: - def __init__(self, context: str = DEFAULT_CONTEXT, project: str | None = None): + """ + Factory for the creation of the Serena MCP server with an associated SerenaAgent. + """ + + def __init__(self, context: str = DEFAULT_CONTEXT, project: str | None = None, memory_log_handler: MemoryLogHandler | None = None): """ :param context: The context name or path to context file :param project: Either an absolute path to the project directory or a name of an already registered project. If the project passed here hasn't been registered yet, it will be registered automatically and can be activated by its name afterward. + :param memory_log_handler: the in-memory log handler to use for the agent's logging """ self.context = SerenaAgentContext.load(context) self.project = project + self.agent: SerenaAgent | None = None + self.memory_log_handler = memory_log_handler @staticmethod def _sanitize_for_openai_tools(schema: dict) -> dict: @@ -225,9 +231,9 @@ class SerenaMCPFactory: title=None, ) - @abstractmethod def _iter_tools(self) -> Iterator[Tool]: - pass + assert self.agent is not None + yield from self.agent.get_exposed_tool_instances() # noinspection PyProtectedMember def _set_mcp_tools(self, mcp: FastMCP, openai_tool_compatible: bool = False) -> None: @@ -239,9 +245,13 @@ class SerenaMCPFactory: mcp._tool_manager._tools[tool.get_name()] = mcp_tool log.info(f"Starting MCP server with {len(mcp._tool_manager._tools)} tools: {list(mcp._tool_manager._tools.keys())}") - @abstractmethod - def _instantiate_agent(self, serena_config: SerenaConfig, modes: list[SerenaAgentMode]) -> None: - pass + def _create_serena_agent(self, serena_config: SerenaConfig, modes: list[SerenaAgentMode]) -> SerenaAgent: + return SerenaAgent( + project=self.project, serena_config=serena_config, context=self.context, modes=modes, memory_log_handler=self.memory_log_handler + ) + + def _create_default_serena_config(self) -> SerenaConfig: + return SerenaConfig.from_config_file() def create_mcp_server( self, @@ -269,7 +279,7 @@ class SerenaMCPFactory: :param tool_timeout: Timeout in seconds for tool execution. If not specified, will take the value from the serena configuration. """ try: - config = SerenaConfig.from_config_file() + config = self._create_default_serena_config() # update configuration with the provided parameters if enable_web_dashboard is not None: @@ -285,7 +295,7 @@ class SerenaMCPFactory: config.tool_timeout = tool_timeout modes_instances = [SerenaAgentMode.load(mode) for mode in modes] - self._instantiate_agent(config, modes_instances) + self.agent = self._create_serena_agent(config, modes_instances) except Exception as e: show_fatal_exception_safe(e) @@ -300,49 +310,13 @@ class SerenaMCPFactory: return mcp @asynccontextmanager - @abstractmethod async def server_lifespan(self, mcp_server: FastMCP) -> AsyncIterator[None]: """Manage server startup and shutdown lifecycle.""" - yield None # ensures MyPy understands we yield None - - @abstractmethod - def _get_initial_instructions(self) -> str: - pass - - -class SerenaMCPFactorySingleProcess(SerenaMCPFactory): - """ - MCP server factory where the SerenaAgent and its language server run in the same process as the MCP server - """ - - def __init__(self, context: str = DEFAULT_CONTEXT, project: str | None = None, memory_log_handler: MemoryLogHandler | None = None): - """ - :param context: The context name or path to context file - :param project: Either an absolute path to the project directory or a name of an already registered project. - If the project passed here hasn't been registered yet, it will be registered automatically and can be activated by its name - afterward. - """ - super().__init__(context=context, project=project) - self.agent: SerenaAgent | None = None - self.memory_log_handler = memory_log_handler - - def _instantiate_agent(self, serena_config: SerenaConfig, modes: list[SerenaAgentMode]) -> None: - self.agent = SerenaAgent( - project=self.project, serena_config=serena_config, context=self.context, modes=modes, memory_log_handler=self.memory_log_handler - ) - - def _iter_tools(self) -> Iterator[Tool]: - assert self.agent is not None - yield from self.agent.get_exposed_tool_instances() - - def _get_initial_instructions(self) -> str: - assert self.agent is not None - # we don't use the tool (which at the time of writing calls this method), since the tool may be disabled by the config - return self.agent.create_system_prompt() - - @asynccontextmanager - async def server_lifespan(self, mcp_server: FastMCP) -> AsyncIterator[None]: openai_tool_compatible = self.context.name in ["chatgpt", "codex", "oaicompat-agent"] self._set_mcp_tools(mcp_server, openai_tool_compatible=openai_tool_compatible) log.info("MCP server lifetime setup complete") yield + + def _get_initial_instructions(self) -> str: + assert self.agent is not None + return self.agent.create_system_prompt() From f593067c330b87310ad2df18c11ca381bb48d968 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 24 Dec 2025 15:03:14 +0100 Subject: [PATCH 03/10] Increase default timeout to 300s (as in the plugin) --- src/serena/tools/jetbrains_plugin_client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/serena/tools/jetbrains_plugin_client.py b/src/serena/tools/jetbrains_plugin_client.py index cd31b7b1..f889e725 100644 --- a/src/serena/tools/jetbrains_plugin_client.py +++ b/src/serena/tools/jetbrains_plugin_client.py @@ -41,9 +41,13 @@ class JetBrainsPluginClient(ToStringMixin): """ BASE_PORT = 0x5EA2 + PLUGIN_REQUEST_TIMEOUT = 300 + """ + the timeout used for request handling within the plugin (a constant in the plugin) + """ last_port: int | None = None - def __init__(self, port: int, timeout: int = 30): + def __init__(self, port: int, timeout: int = PLUGIN_REQUEST_TIMEOUT): self.base_url = f"http://127.0.0.1:{port}" self.timeout = timeout self.session = requests.Session() From 745e50d1ee223ff6f7a36493f833b9725cdfd498 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 25 Dec 2025 19:08:01 +0100 Subject: [PATCH 04/10] Improve project activation instructions --- docs/02-usage/040_workflow.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/02-usage/040_workflow.md b/docs/02-usage/040_workflow.md index 35afb55e..4544b25b 100644 --- a/docs/02-usage/040_workflow.md +++ b/docs/02-usage/040_workflow.md @@ -55,18 +55,18 @@ Indexing has to be called only once. During regular usage, Serena will automatic Project activation makes Serena aware of the project you want to work with. You can either choose to do this - * while in a conversation, by telling the model to activate a project, e.g., + * while in a conversation, by telling the LLM to activate a project, e.g., * "Activate the project /path/to/my_project" (for first-time activation with auto-creation) * "Activate the project my_project" Note that this option requires the `activate_project` tool to be active, - which it isn't in the (default version) of context `ide-assistant` if a project is provided at startup. - (The tool is deactivated because we assume that in the ide-assistant context the user will only work on the open project and have + which it isn't in single-project [contexts](contexts) like `ide` or `claude-code` *if* a project is provided at startup. + (The tool is deactivated, because we assume that in these contexts, user will only work on the single, open project and have no need to switch it.) * when the MCP server starts, by passing the project path or name as a command-line argument - (e.g. when working on a fixed project in `ide-assistant` mode): `--project ` + (e.g. when using a single-project mode like `ide` or `claude-code`): `--project ` ## Onboarding & Memories From ef1ed31010d7249123bef5a06a1296b79092964a Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 25 Dec 2025 19:08:41 +0100 Subject: [PATCH 05/10] Add paragraph on single-project contexts, improve generality --- docs/02-usage/050_configuration.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/02-usage/050_configuration.md b/docs/02-usage/050_configuration.md index ad384aaa..1a62f6b0 100644 --- a/docs/02-usage/050_configuration.md +++ b/docs/02-usage/050_configuration.md @@ -8,14 +8,15 @@ Serena is configured in four places: 1. The `serena_config.yml` for general settings that apply to all clients and projects. It is located in your user directory under `.serena/serena_config.yml`. - If you do not explicitly create the file, it will be auto-generated when you first run Serena. + The file will be auto-generated when you first run Serena. You can edit it directly or use ```shell - uvx --from git+https://github.com/oraios/serena serena config edit + config edit ``` - (or use the `--directory` command version). + where `` is [your way of running Serena](020_running). + The configuration file can also be accessed through [Serena's dashboard](060_dashboard). 2. In the arguments passed to the `start-mcp-server` in your client's config (see below), which will apply to all sessions started by the respective client. In particular, the [context](contexts) parameter should be set appropriately for Serena to be best adjusted to existing tools and capabilities of your client. @@ -55,6 +56,12 @@ Choose the context that best matches the type of integration you are using. Find the concrete definitions of the above contexts [here](https://github.com/oraios/serena/tree/main/src/serena/resources/config/contexts). +Note that the contexts `ide` and `claude-code` are **single-project contexts** (defining `single_project: true`). +For such contexts, if a project is provided at startup, the set of tools is limited to those required by the project's +concrete configuration, and other tools are excluded completely, allowing the set of tools to be minimal. +Tools explicitly disabled by the project will not be available at all. Since changing the active project +ceases to be a relevant operation in this case, the project activation tool is disabled. + When launching Serena, specify the context using `--context `. Note that for cases where parameter lists are specified (e.g. Claude Desktop), you must add two parameters to the list. From 5b3b81e7e4b42c3a4cb7eda47ddae4f427e8dd37 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sat, 27 Dec 2025 11:47:46 +0100 Subject: [PATCH 06/10] Revert "Do not use listen_address 0.0.0.0 for Docker (has no desirable effect)" This reverts commit 7ad44edd18fe9e28d0c77c3e24893f0248a792ad. Listen address 0.0.0.0 is necessary for docker, because port forwarding for Docker is just NAT, so port forwarding alone is not sufficient Fixes #879 --- DOCKER.md | 2 ++ Dockerfile | 1 + 2 files changed, 3 insertions(+) diff --git a/DOCKER.md b/DOCKER.md index 08348182..bb77040c 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -27,6 +27,8 @@ special settings for Docker usage: ``` # Disable the GUI log window since it's not supported in Docker gui_log_window: False +# Listen on all interfaces for the web dashboard to be accessible from outside the container +web_dashboard_listen_address: 0.0.0.0 # Disable opening the web dashboard on launch (not possible within the container) web_dashboard_open_on_launch: False ``` diff --git a/Dockerfile b/Dockerfile index e58d821b..1dccaf40 100644 --- a/Dockerfile +++ b/Dockerfile @@ -64,6 +64,7 @@ ENV SERENA_HOME=/workspaces/serena/config RUN mkdir -p $SERENA_HOME RUN cp src/serena/resources/serena_config.template.yml $SERENA_HOME/serena_config.yml RUN sed -i 's/^gui_log_window: .*/gui_log_window: False/' $SERENA_HOME/serena_config.yml +RUN sed -i 's/^web_dashboard_listen_address: .*/web_dashboard_listen_address: 0.0.0.0/' $SERENA_HOME/serena_config.yml RUN sed -i 's/^web_dashboard_open_on_launch: .*/web_dashboard_open_on_launch: False/' $SERENA_HOME/serena_config.yml # Create virtual environment and install dependencies From b561af9ab5416c3795e8711a97a6d1e852a2e61d Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sat, 27 Dec 2025 17:16:43 +0100 Subject: [PATCH 07/10] Update MCP factory usage in CLI --- src/serena/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/serena/cli.py b/src/serena/cli.py index bc4af3e6..217cb409 100644 --- a/src/serena/cli.py +++ b/src/serena/cli.py @@ -27,7 +27,7 @@ from serena.constants import ( SERENAS_OWN_CONTEXT_YAMLS_DIR, SERENAS_OWN_MODE_YAMLS_DIR, ) -from serena.mcp import SerenaMCPFactory, SerenaMCPFactorySingleProcess +from serena.mcp import SerenaMCPFactory from serena.project import Project from serena.tools import FindReferencingSymbolsTool, FindSymbolTool, GetSymbolsOverviewTool, SearchForPatternTool, ToolRegistry from serena.util.logging import MemoryLogHandler @@ -237,7 +237,7 @@ class TopLevelCommands(AutoRegisteringGroup): log.info("Auto-detected project root: %s", project) project_file = project_file_arg or project - factory = SerenaMCPFactorySingleProcess(context=context, project=project_file, memory_log_handler=memory_log_handler) + factory = SerenaMCPFactory(context=context, project=project_file, memory_log_handler=memory_log_handler) server = factory.create_mcp_server( host=host, port=port, From 1289b118ad22ddc15171792df8051a37dc93a61a Mon Sep 17 00:00:00 2001 From: Tomasz Wyderka Date: Sat, 27 Dec 2025 20:44:43 +0100 Subject: [PATCH 08/10] Fix broken import in test_tool_parameter_types.py The test was using SerenaMCPFactorySingleProcess and _instantiate_agent() which were removed when process isolation was removed from the codebase. Updated to use SerenaMCPFactory and _create_serena_agent() instead. This fixes the CI pipeline which was failing on all test runs. --- test/serena/test_tool_parameter_types.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/serena/test_tool_parameter_types.py b/test/serena/test_tool_parameter_types.py index ec714be4..4fdf0dc8 100644 --- a/test/serena/test_tool_parameter_types.py +++ b/test/serena/test_tool_parameter_types.py @@ -3,7 +3,7 @@ import logging import pytest from serena.config.serena_config import SerenaConfig -from serena.mcp import SerenaMCPFactorySingleProcess +from serena.mcp import SerenaMCPFactory from serena.tools.tools_base import ToolRegistry @@ -16,9 +16,9 @@ def test_all_tool_parameters_have_type(context): cfg = SerenaConfig(gui_log_window_enabled=False, web_dashboard=False, log_level=logging.ERROR) registry = ToolRegistry() cfg.included_optional_tools = tuple(registry.get_tool_names_optional()) - factory = SerenaMCPFactorySingleProcess(context=context) + factory = SerenaMCPFactory(context=context) # Initialize the agent so that the tools are available - factory._instantiate_agent(cfg, []) + factory.agent = factory._create_serena_agent(cfg, []) tools = list(factory._iter_tools()) for tool in tools: From c6b27500617b34ee094413df74a6299824aa730b Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sun, 28 Dec 2025 16:03:30 +0100 Subject: [PATCH 09/10] Improve Claude Code connection instructions, adding a paragraph on max token efficiency --- docs/02-usage/030_clients.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/02-usage/030_clients.md b/docs/02-usage/030_clients.md index b757934b..e1e0b6aa 100644 --- a/docs/02-usage/030_clients.md +++ b/docs/02-usage/030_clients.md @@ -75,7 +75,8 @@ object, e.g. Serena is a great way to make Claude Code both cheaper and more powerful! -From your project directory, add serena with a command like this, +**Per-Project Configuration.** To add the Serena MCP server to the current project in the current directory, +use this command: ```shell claude mcp add serena -- uvx --from git+https://github.com/oraios/serena serena start-mcp-server --context claude-code --project "$(pwd)" @@ -88,18 +89,31 @@ Note: that Serena is configured to work on the current project from the get-go, following Claude Code's mode of operation. -Alternatively, use `--project-from-cwd` for user-level configuration that works across all projects: +**Global Configuration**. Alternatively, use `--project-from-cwd` for user-level configuration that works across all projects: ```shell claude mcp add --scope user serena -- start-mcp-server --context=claude-code --project-from-cwd ``` -This searches up from the current directory for `.serena/project.yml` or `.git` markers, -falling back to the current directory if neither is found. This makes it suitable for a -single global MCP configuration. The `--project-from-cwd` option is intended for CLI-based agents -(like Claude Code, Gemina or Codex) that invoke Serena from within project directories. +Whenever you start Claude Code, Serena will search up from the current directory for `.serena/project.yml` or `.git` markers, +activating the current directory as the project if neither is found. +This mechanism makes it suitable for a single global MCP configuration. -Be sure to use at least `v1.0.52` of Claude Code (as earlier versions do not read MCP server system prompts upon startup). +**Maximum Token Efficiency.** To maximize token efficiency, you may want to use Claude Code's +*on-demand tool loading* feature, which is supported since at least v2.0.74 of Claude Code. +This feature avoids sending all tool descriptions to Claude upon startup, thus saving tokens. +Instead, Claude will search for tools as needed (but there are no guarantees that it will +search optimally, of course). +To enable this feature, set the environment variable `ENABLE_TOOL_SEARCH=true`. +Depending on your shell, you can also set this on a per-session basis, e.g. using +```shell +ENABLE_TOOL_SEARCH=true claude +``` +in bash/zsh, or using +```shell +set ENABLE_TOOL_SEARCH=true && claude +``` +in Windows CMD to launch Claude Code. ## VSCode From 6ae5757ed5d79695a98735c3557ad4cb93346454 Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Thu, 1 Jan 2026 19:00:46 +0700 Subject: [PATCH 10/10] Pinned ruamel to 0.18.14 From 0.19 on ruamel requires C++ build tools on windows --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7c44e907..086dd9c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ dependencies = [ "pydantic>=2.10.6", "types-pyyaml>=6.0.12.20241230", "pyyaml>=6.0.2", - "ruamel.yaml>=0.18.0", + "ruamel.yaml==0.18.14", "jinja2>=3.1.6", "dotenv>=0.9.9", "pathspec>=0.12.1", diff --git a/uv.lock b/uv.lock index 19393a69..e8083592 100644 --- a/uv.lock +++ b/uv.lock @@ -2014,7 +2014,7 @@ requires-dist = [ { name = "python-dotenv", specifier = ">=1.0.0,<2" }, { name = "pyyaml", specifier = ">=6.0.2" }, { name = "requests", specifier = ">=2.32.3,<3" }, - { name = "ruamel-yaml", specifier = ">=0.18.0" }, + { name = "ruamel-yaml", specifier = "==0.18.14" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.0.285" }, { name = "sensai-utils", specifier = ">=1.5.0" }, { name = "sphinx", marker = "extra == 'dev'", specifier = ">=7,<8" },