diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index a89e7fd6..724837aa 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -44,26 +44,13 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable={{is_default_branch}}
- - name: Build and push production image
+ - name: Build and push image from main
uses: docker/build-push-action@v5
with:
context: .
- target: production
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
-
- - name: Build and push development image
- uses: docker/build-push-action@v5
- with:
- context: .
- target: development
- platforms: linux/amd64,linux/arm64
- push: ${{ github.event_name != 'pull_request' }}
- tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
- labels: ${{ steps.meta.outputs.labels }}
- cache-from: type=gha
- cache-to: type=gha,mode=max
\ No newline at end of file
diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
index 4c36b51a..622b6c4b 100644
--- a/.github/workflows/pytest.yml
+++ b/.github/workflows/pytest.yml
@@ -21,6 +21,18 @@ jobs:
python-version: ["3.11"]
steps:
- uses: actions/checkout@v3
+ - name: Free disk space
+ if: runner.os == 'Linux'
+ run: |
+ df -h
+ sudo rm -rf /usr/local/lib/android
+ sudo rm -rf /usr/share/dotnet
+ sudo rm -rf /opt/ghc
+ sudo rm -rf /opt/hostedtoolcache
+ sudo apt-get clean
+ sudo apt-get autoremove -y
+ docker system prune -af || true
+ df -h
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
@@ -78,8 +90,8 @@ jobs:
if: runner.os != 'Windows'
uses: erlef/setup-beam@v1
with:
- elixir-version: "1.18.4"
- otp-version: "26.1"
+ elixir-version: "1.19.3"
+ otp-version: "28"
# Erlang currently not tested in CI, random hangings on macos, always hangs on ubuntu
# In local tests, erlang seems to work though
# - name: Install Erlang Language Server
@@ -103,6 +115,10 @@ jobs:
with:
distribution: 'temurin'
java-version: '17'
+ - name: Setup .NET SDK (for F# and C# languages)
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '8.0.x'
- name: Install Terraform
uses: hashicorp/setup-terraform@v3
with:
@@ -359,6 +375,11 @@ jobs:
key: language-servers-${{ runner.os }}-v1
restore-keys: |
language-servers-${{ runner.os }}-
+ - name: Report free disk space
+ if: runner.os == 'Linux'
+ run: |
+ echo "Free disk space before tests:"
+ df -h
- name: Test with pytest
shell: bash
run: uv run poe test
diff --git a/.gitignore b/.gitignore
index 89bbe0b2..ec2ca442 100644
--- a/.gitignore
+++ b/.gitignore
@@ -202,20 +202,12 @@ pylint.html
/agent-ui
/test/**/.serena
-# dynamic LS installations
-/src/multilspy/language_servers/*/static
-/src/solidlsp/language_servers/*/static
-/src/solidlsp/language_servers/static
-
# clojure-lsp temporary files
.calva/
.clj-kondo/
.cpcache/
.lsp/
-# Cache is in .multilspy
-**/.multilspy/**
-
# temporary and backup files
*.bak
*.tmp
@@ -259,4 +251,7 @@ test/resources/repos/scala/.bloop/
stack.yaml.lock
dist-newstyle/
cabal.project.local*
-.ghc.environment.*
\ No newline at end of file
+.ghc.environment.*
+
+zz-misc/
+vue-implementation/
\ No newline at end of file
diff --git a/.serena/memories/adding_new_language_support_guide.md b/.serena/memories/adding_new_language_support_guide.md
index 21980531..18d12436 100644
--- a/.serena/memories/adding_new_language_support_guide.md
+++ b/.serena/memories/adding_new_language_support_guide.md
@@ -21,39 +21,35 @@ Have a look at `intelephense.py` for a reference implementation of a language se
dependencies, and on `pyright_server.py` that does not need any additional dependencies
because the language server can be installed directly as python package.
-
```python
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
-from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
+
class NewLanguageServer(SolidLanguageServer):
"""
Language server implementation for NewLanguage.
"""
-
- def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
+
+ def __init__(self, config: LanguageServerConfig, repository_root_path: str):
# Determine language server command
cmd = self._get_language_server_command()
-
- super().__init__(
- config,
- logger,
- repository_root_path,
+
+ super().__init__(config,
ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path),
- "new_language", # Language ID for LSP
- )
-
+ "new_language",,
+
def _get_language_server_command(self) -> list[str]:
"""Get the command to start the language server."""
# Example: return ["new-language-server", "--stdio"]
pass
-
+
@override
def is_ignored_dirname(self, dirname: str) -> bool:
"""Define language-specific directories to ignore."""
- return super().is_ignored_dirname(dirname) or dirname in ["build", "dist", "target"]
+ return super().is_ignored_dirname(dirname) or dirname in ["build",
+ "dist", "target"]
```
### 1.2 Language Server Discovery and Installation
@@ -62,7 +58,7 @@ For languages requiring automatic installation, implement download logic similar
```python
@classmethod
-def _ensure_server_installed(cls, logger: LanguageServerLogger) -> str:
+def _ensure_server_installed(cls) -> str:
"""Ensure language server is installed and return path."""
# Check system installation first
system_server = shutil.which("new-language-server")
@@ -70,10 +66,10 @@ def _ensure_server_installed(cls, logger: LanguageServerLogger) -> str:
return system_server
# Download and install if needed
- server_path = cls._download_and_install_server(logger)
+ server_path = cls._download_and_install_server()
return server_path
-def _download_and_install_server(cls, logger: LanguageServerLogger) -> str:
+def _download_and_install_server(cls) -> str:
"""Download and install the language server."""
# Implementation specific to your language server
pass
@@ -129,12 +125,12 @@ In `src/solidlsp/ls.py`, add your language to the `create` method:
```python
@classmethod
-def create(cls, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str) -> "SolidLanguageServer":
+def create(cls, config: LanguageServerConfig, repository_root_path: str) -> "SolidLanguageServer":
match config.code_language:
# Existing cases...
case Language.NEW_LANGUAGE:
from solidlsp.language_servers.new_language_server import NewLanguageServer
- return NewLanguageServer(config, logger, repository_root_path)
+ return NewLanguageServer(config, repository_root_path)
```
## Step 3: Test Repository
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d8137826..e6228cd0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,8 @@ Status of the `main` branch. Changes prior to the next official version change w
* Add monorepo/multi-language support
* Project configuration files (`project.yml`) can now define multiple languages.
Auto-detection adds only the most prominent language by default.
- * Additional languages can be conveniently added via the Dashboard while a project is already activated.
+ * Additional languages can be conveniently added via the Dashboard while a project is already activated.
+ * The current project can be activated automatically even when the MCP configuration is global (through the --project-from-cwd flag)
* Support overloaded symbols in `FindSymbolTool` and related tools
* Name paths of overloaded symbols now include an index (e.g., `myOverloadedFunction[2]`)
* Responses of the Java language server, which handled this in its own way, are now adapted accordingly,
@@ -35,9 +36,11 @@ Status of the `main` branch. Changes prior to the next official version change w
* Language support:
* **Add support for Fortran** via fortls language server (requires `pip install fortls`)
+ * **Add partial support for Groovy** requires user-provided Groovy language server JAR (see [setup guide](docs/03-special-guides/groovy_setup_guide_for_serena.md))
* **Add support for Julia** via LanguageServer.jl
* **Add support for Haskell** via Haskell Language Server (HLS) with automatic discovery via ghcup, stack, or system PATH; supports both Stack and Cabal projects
* **Add support for Scala** via Metals language server (requires some [manual setup](docs/03-special-guides/scala_setup_guide_for_serena.md))
+ * **Add support for F#** via FsAutoComplete/Ionide LSP server.
* **Add support for Elm** via @elm-tooling/elm-language-server (automatically downloads if not installed; requires Elm compiler)
* **Add support for Perl** via Perl::LanguageServer with LSP integration for .pl, .pm, and .t files
* **Add support for AL (Application Language)** for Microsoft Dynamics 365 Business Central development. Requires VS Code AL extension (ms-dynamics-smb.al).
@@ -46,11 +49,13 @@ Status of the `main` branch. Changes prior to the next official version change w
* **Add support for Lua** via lua-language-server
* **Add support for Nix** requires nixd installation (Windows not supported)
* **Add experimental support for YAML** via yaml-language-server with LSP integration for .yaml and .yml files
+ * **Add support for TOML** via Taplo language server with automatic binary download, validation, formatting, and schema support for .toml files
* **Dart now officially supported**: Dart was always working, but now tests were added, and it is promoted to "officially supported"
* **Rust now uses already installed rustup**: The rust-analyzer is no longer bundled with Serena. Instead, it uses the rust-analyzer from your Rust toolchain managed by rustup. This ensures compatibility with your Rust version and eliminates outdated bundled binaries.
* **Kotlin now officially supported**: We now use the official Kotlin LS, tests run through and performance is good, even though the LS is in an early development stage.
* **Add support for Erlang** experimental, may hang or be slow, uses the recently archived [erlang_ls](https://github.com/erlang-ls/erlang_ls)
* **Ruby dual language server support**: Added ruby-lsp as the modern primary Ruby language server. Solargraph remains available as an experimental legacy option. ruby-lsp supports both .rb and .erb files, while Solargraph supports .rb files only.
+ * **Add support for PowerShell** via PowerShell Editor Services (PSES). Requires `pwsh` (PowerShell Core) to be installed and available in PATH. Supports symbol navigation, go-to-definition, and within-file references for .ps1 files.
# 0.1.4
diff --git a/CLAUDE.md b/CLAUDE.md
index edf91cd1..8f1ef25c 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -9,11 +9,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- `uv run poe type-check` - Run mypy type checking - ONLY allowed type checking command
- `uv run poe test` - Run tests with default markers (excludes java/rust by default)
- `uv run poe test -m "python or go"` - Run specific language tests
+- `uv run poe test -m vue` - Run Vue tests
- `uv run poe lint` - Check code style without fixing
**Test Markers:**
Available pytest markers for selective testing:
-- `python`, `go`, `java`, `rust`, `typescript`, `php`, `perl`, `csharp`, `elixir`, `terraform`, `clojure`, `swift`, `bash`, `ruby`, `ruby_solargraph`
+- `python`, `go`, `java`, `rust`, `typescript`, `vue`, `php`, `perl`, `powershell`, `csharp`, `elixir`, `terraform`, `clojure`, `swift`, `bash`, `ruby`, `ruby_solargraph`
- `snapshot` - for symbolic editing operation tests
**Project Management:**
@@ -100,7 +101,7 @@ Configuration is loaded from (in order of precedence):
- **Symbol-based editing** - Uses LSP for precise code manipulation
- **Caching strategy** - Reduces language server overhead
- **Error recovery** - Automatic language server restart on crashes
-- **Multi-language support** - 16+ languages with LSP integration
+- **Multi-language support** - 19 languages with LSP integration (including Vue)
- **MCP protocol** - Exposes tools to AI agents via Model Context Protocol
- **Async operation** - Non-blocking language server interactions
diff --git a/DOCKER.md b/DOCKER.md
index 84f8f746..bb77040c 100644
--- a/DOCKER.md
+++ b/DOCKER.md
@@ -1,6 +1,6 @@
# Docker Setup for Serena (Experimental)
-⚠️ **EXPERIMENTAL FEATURE**: The Docker setup for Serena is currently experimental and has several limitations. Please read this entire document before using Docker with Serena.
+⚠️ **EXPERIMENTAL FEATURE**: The Docker setup for Serena is still experimental and has some limitations. Please read this entire document before using Docker with Serena.
## Overview
@@ -12,27 +12,42 @@ Docker support allows you to run Serena in an isolated container environment, wh
- **Consistent dependencies**: No need to manage language servers and dependencies on your host system
- **Cross-platform support**: Works consistently across Windows, macOS, and Linux
-## Important Limitations and Caveats
+## Important Usage Pointers
-### 1. Configuration File Conflicts
+### Configuration
-⚠️ **Critical**: Docker uses a separate configuration file (`serena_config.docker.yml`) to avoid path conflicts. When running in Docker:
-- Container paths will be stored in the configuration (e.g., `/workspaces/serena/...`)
-- These paths are incompatible with non-Docker usage
-- After using Docker, you cannot directly switch back to non-Docker usage without manual configuration adjustment
+Serena's configuration and log files are stored in the container in `/workspaces/serena/config/`.
+Any local configuration you may have for Serena will not apply; the container uses its own separate configuration.
-### 2. Project Activation Limitations
+You can mount a local configuration/data directory to persist settings across container restarts
+(which will also contain session log files).
+Simply mount your local directory to `/workspaces/serena/config` in the container.
+Initially, be sure to add a `serena_config.yml` file to the mounted directory which applies the following
+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
+```
+Set other configuration options as needed.
+
+### Project Activation Limitations
- **Only mounted directories work**: Projects must be mounted as volumes to be accessible
- Projects outside the mounted directories cannot be activated or accessed
-- Default setup only mounts the current directory
+- Since projects are not remembered across container restarts (unless you mount a local configuration as described above),
+ activate them using the full path (e.g. `/workspaces/projects/my-project`) when using dynamic project activation
-### 3. GUI Window Disabled
+### Language Support Limitations
-- The GUI log window option is automatically disabled in Docker environments
-- Use the web dashboard instead (see below)
+The default Docker image does not include dependencies for languages that
+require explicit system-level installations.
+Only languages that install their requirements on the fly will work out of the box.
-### 4. Dashboard Port Configuration
+### Dashboard Port Configuration
The web dashboard runs on port 24282 (0x5EDA) by default. You can configure this using environment variables:
@@ -46,7 +61,7 @@ SERENA_DASHBOARD_PORT=8080 docker-compose up serena
⚠️ **Note**: If the local port is occupied, you'll need to specify a different port using the environment variable.
-### 5. Line Ending Issues on Windows
+### Line Ending Issues on Windows
⚠️ **Windows Users**: Be aware of potential line ending inconsistencies:
- Files edited within the Docker container may use Unix line endings (LF)
@@ -70,7 +85,7 @@ SERENA_DASHBOARD_PORT=8080 docker-compose up serena
Note: Edit the `compose.yaml` file to customize volume mounts for your projects.
-### Using Docker directly
+### Building the Docker Image Manually
```bash
# Build the image
@@ -98,7 +113,7 @@ services:
- /path/to/another/project:/workspace/another-project
# Add the context for the IDE assistant option:
command:
- - "uv run --directory . serena-mcp-server --transport sse --port 9121 --host 0.0.0.0 --context ide-assistant"
+ - "uv run --directory . serena-mcp-server --transport sse --port 9121 --host 0.0.0.0 --context claude-code"
```
See the [Docker Merge Compose files documentation](https://docs.docker.com/compose/how-tos/multiple-compose-files/merge/) for more details on using merge files.
@@ -157,24 +172,3 @@ Ensure projects are properly mounted:
- Check volume mounts in `docker-compose.yaml`
- Use absolute paths for external projects
- Verify permissions on mounted directories
-
-## Migration Path
-
-To switch between Docker and non-Docker usage:
-
-1. **Docker to Non-Docker**:
- - Manually edit project paths in `serena_config.yml`
- - Change container paths to host paths
- - Or use separate config files for each environment
-
-2. **Non-Docker to Docker**:
- - Projects will be re-registered with container paths
- - Original config remains unchanged
-
-## Future Improvements
-
-We're working on:
-- Automatic config migration between environments
-- Better project path handling
-- Dynamic port allocation
-- Windows line-ending handling.
diff --git a/Dockerfile b/Dockerfile
index d84ed7d0..1dccaf40 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -53,28 +53,21 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
# Set the working directory
WORKDIR /workspaces/serena
-# Development target
-FROM base AS development
# Copy all files for development
COPY . /workspaces/serena/
-# Create virtual environment and install dependencies with dev extras
-RUN uv venv
-RUN . .venv/bin/activate
-RUN uv pip install --all-extras -r pyproject.toml -e .
-ENV PATH="/workspaces/serena/.venv/bin:${PATH}"
+# Install sed
+RUN apt-get update && apt-get install -y sed
-# Entrypoint to ensure environment is activated
-ENTRYPOINT ["/bin/bash", "-c", "source .venv/bin/activate && $0 $@"]
+# Create Serena configuration
+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
-# Production target
-FROM base AS production
-# Copy only necessary files for production
-COPY pyproject.toml /workspaces/serena/
-COPY README.md /workspaces/serena/
-COPY src/ /workspaces/serena/src/
-
-# Create virtual environment and install dependencies (production only)
+# Create virtual environment and install dependencies
RUN uv venv
RUN . .venv/bin/activate
RUN uv pip install -r pyproject.toml -e .
@@ -82,4 +75,3 @@ ENV PATH="/workspaces/serena/.venv/bin:${PATH}"
# Entrypoint to ensure environment is activated
ENTRYPOINT ["/bin/bash", "-c", "source .venv/bin/activate && $0 $@"]
-
diff --git a/README.md b/README.md
index 92970158..48b17305 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,9 @@ Instead, it can use code-centric tools like `find_symbol`, `find_referencing_sym
+> [!TIP]
+> The [**Serena JetBrains plugin**](#the-serena-jetbrains-plugin) has been released!
+
## LLM Integration
Serena provides the necessary [tools](https://oraios.github.io/serena/01-about/035_tools.html) for coding workflows, but an LLM is required to do the actual work,
@@ -55,23 +58,47 @@ Note how Serena's tools enable Claude to find and edit the right symbols.
https://github.com/user-attachments/assets/6eaa9aa1-610d-4723-a2d6-bf1e487ba753
-### Programming Language Support & Semantic Analysis Capabilities
+## Programming Language Support & Semantic Analysis Capabilities
-Serena's semantic code analysis capabilities build on **language servers** using the widely implemented
-language server protocol (LSP). The LSP provides a set of versatile code querying
-and editing functionalities based on symbolic understanding of the code.
+Serena provides a set of versatile code querying and editing functionalities
+based on symbolic understanding of the code.
Equipped with these capabilities, Serena discovers and edits code just like a seasoned developer
making use of an IDE's capabilities would.
Serena can efficiently find the right context and do the right thing even in very large and
-complex projects! So not only is it free and open-source, it frequently achieves better results
-than existing solutions that charge a premium.
+complex projects!
+
+There are two alternative technologies powering these capabilities:
+
+* **Language servers** implementing the language server Protocol (LSP) — the free/open-source alternative.
+* **The Serena JetBrains Plugin**, which leverages the powerful code analysis and editing
+ capabilities of your JetBrains IDE.
+
+You can choose either of these backends depending on your preferences and requirements.
+
+### Language Servers
+
+Serena incorporates a powerful abstraction layer for the integration of language servers
+that implement the language server protocol (LSP).
+The underlying language servers are typically open-source projects (like Serena) or at least freely available for use.
-Language servers provide support for a wide range of programming languages.
With Serena's LSP library, we provide **support for over 30 programming languages**, including
-AL, Bash, C#, C/C++, Clojure, Dart, Elixir, Elm, Erlang, Fortran, Go, Haskell, Java, Javascript, Julia, Kotlin, Lua, Markdown, Nix, Perl, PHP, Python, R, Ruby, Rust, Scala, Swift, TypeScript, YAML and Zig.
+AL, Bash, C#, C/C++, Clojure, Dart, Elixir, Elm, Erlang, Fortran, Go, Groovy (partial support), Haskell, Java, Javascript, Julia, Kotlin, Lua, Markdown, Nix, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, Swift, TOML, TypeScript, YAML, and Zig.
> [!IMPORTANT]
-> Some languages require additional dependencies to be installed; see the [Language Support](https://oraios.github.io/serena/01-about/020_programming-languages.html) page for details.
+> Some language servers require additional dependencies to be installed; see the [Language Support](https://oraios.github.io/serena/01-about/020_programming-languages.html) page for details.
+
+### The Serena JetBrains Plugin
+
+As an alternative to language servers, the [Serena JetBrains Plugin](https://plugins.jetbrains.com/plugin/28946-serena/)
+leverages the powerful code analysis capabilities of your JetBrains IDE.
+The plugin naturally supports all programming languages and frameworks that are supported by JetBrains IDEs,
+including IntelliJ IDEA, PyCharm, Android Studio, WebStorm, PhpStorm, RubyMine, GoLand, CLion, and others.
+Only Rider is not supported.
+
+
+
+The plugin offers the most robust and most powerful Serena experience.
+See our [documentation page](https://oraios.github.io/serena/02-usage/025_jetbrains_plugin.html) for further details and instructions.
## Quick Start
diff --git a/compose.yaml b/compose.yaml
index 48923e27..d7c8becd 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -1,7 +1,7 @@
services:
serena:
image: serena:latest
- # To work with projects, you must mount them as volumes:
+ # To work with projects, you must mount them into /workspace/ in the container:
# volumes:
# - ./my-project:/workspace/my-project
# - /path/to/another/project:/workspace/another-project
@@ -16,23 +16,6 @@ services:
- SERENA_DOCKER=1
command:
- "uv run --directory . serena-mcp-server --transport sse --port 9121 --host 0.0.0.0"
- # Add the context for the IDE assistant
- # - "uv run --directory . serena-mcp-server --transport sse --port 9121 --host 0.0.0.0 --context ide-assistant"
+ # Alternatively add further arguments, e.g. a context
+ # - "uv run --directory . serena-mcp-server --transport sse --port 9121 --host 0.0.0.0 --context ide"
- serena-dev:
- image: serena:dev
- build:
- context: ./
- dockerfile: Dockerfile
- target: development
- tty: true
- stdin_open: true
- environment:
- - SERENA_DOCKER=1
- volumes:
- - .:/workspaces/serena
- ports:
- - "${SERENA_PORT:-9121}:9121" # MCP server port
- - "${SERENA_DASHBOARD_PORT:-24282}:24282" # Dashboard port
- command:
- - "uv run --directory . serena-mcp-server"
diff --git a/docs/01-about/020_programming-languages.md b/docs/01-about/020_programming-languages.md
index 764efd14..a748505e 100644
--- a/docs/01-about/020_programming-languages.md
+++ b/docs/01-about/020_programming-languages.md
@@ -1,16 +1,31 @@
# Language Support
-Serena's semantic code analysis capabilities build on **language servers** using the widely implemented
-language server protocol (LSP). The LSP provides a set of versatile code querying
-and editing functionalities based on symbolic understanding of the code.
+Serena provides a set of versatile code querying and editing functionalities
+based on symbolic understanding of the code.
Equipped with these capabilities, Serena discovers and edits code just like a seasoned developer
making use of an IDE's capabilities would.
Serena can efficiently find the right context and do the right thing even in very large and
-complex projects! So not only is it free and open-source, it frequently achieves better results
-than existing solutions that charge a premium.
+complex projects!
-Language servers provide support for a wide range of programming languages.
-With Serena, we provide direct, out-of-the-box support for:
+There are two alternative technologies powering these capabilities:
+
+* **Language servers** implementing the language server Protocol (LSP) — the free/open-source alternative.
+* **The Serena JetBrains Plugin**, which leverages the powerful code analysis and editing
+ capabilities of your JetBrains IDE.
+
+You can choose either of these backends depending on your preferences and requirements.
+
+## Language Servers
+
+Serena incorporates a powerful abstraction layer for the integration of language servers
+that implement the language server protocol (LSP).
+It even supports multiple language servers in parallel to support polyglot projects.
+
+The language servers themselves are typically open-source projects (like Serena)
+or at least freely available for use.
+
+We currently provide direct, out-of-the-box support for the programming languages listed below.
+Some languages require additional installations or setup steps, as noted.
* **AL**
* **Bash**
@@ -20,15 +35,19 @@ With Serena, we provide direct, out-of-the-box support for:
* **Clojure**
* **Dart**
* **Elixir**
- (requires installation of NextLS and Elixir; Windows not supported)
+ (requires Elixir installation; Expert language server is downloaded automatically)
* **Elm**
(requires Elm compiler)
* **Erlang**
(requires installation of beam and [erlang_ls](https://github.com/erlang-ls/erlang_ls); experimental, might be slow or hang)
-* **Fortran**
+* **F#**
+ (requires .NET SDK 8.0+; uses FsAutoComplete/Ionide, which is auto-installed; for Homebrew .NET on macOS, set DOTNET_ROOT in your environment)
+* **Fortran**
(requires installation of fortls: `pip install fortls`)
* **Go**
(requires installation of `gopls`)
+* **Groovy**
+ (requires local groovy-language-server.jar setup via GROOVY_LS_JAR_PATH or configuration)
* **Haskell**
(automatically locates HLS via ghcup, stack, or system PATH; supports Stack and Cabal projects)
* **Java**
@@ -56,8 +75,42 @@ With Serena, we provide direct, out-of-the-box support for:
(requires some [manual setup](../03-special-guides/scala_setup_guide_for_serena); uses Metals LSP)
* **Swift**
* **TypeScript**
+* **Vue**
+ (3.x with TypeScript; requires Node.js v18+ and npm; supports .vue Single File Components with monorepo detection)
+* **YAML**
* **Zig**
(requires installation of ZLS - Zig Language Server)
Support for further languages can easily be added by providing a shallow adapter for a new language server implementation,
see Serena's [memory on that](https://github.com/oraios/serena/blob/main/.serena/memories/adding_new_language_support_guide.md).
+
+## The Serena JetBrains Plugin
+
+As an alternative to language servers, the [Serena JetBrains Plugin](https://plugins.jetbrains.com/plugin/28946-serena/)
+leverages the powerful code analysis capabilities of JetBrains IDEs.
+The plugin naturally supports all programming languages and frameworks that are supported by JetBrains IDEs,
+including IntelliJ IDEA, PyCharm, Android Studio, AppCode, WebStorm, PhpStorm, RubyMine, GoLand, AppCode, CLion, Rider, and others.
+
+When using the plugin, Serena connects to an instance of your JetBrains IDE via the plugin. For users who already
+work in a JetBrains IDE, this means Serena seamlessly integrates with the IDE instance you typically have open anyway,
+requiring no additional setup or configuration beyond the plugin itself. This approach offers several key advantages:
+
+* **External library indexing**: Dependencies and libraries are fully indexed and accessible to Serena
+* **No additional setup**: No need to download or configure separate language servers
+* **Enhanced performance**: Faster tool execution thanks to optimized IDE integration
+* **Multi-language excellence**: First-class support for polyglot projects with multiple languages and frameworks
+
+Even if you prefer to work in a different code editor, you can still benefit from the JetBrains plugin by running
+a JetBrains IDE instance (most have free community editions) alongside your preferred editor with your project
+opened and indexed. Serena will connect to the IDE for code analysis while you continue working in your editor
+of choice.
+
+```{raw} html
+
+
+
+
+
+```
+
+See the [JetBrains Plugin documentation](../02-usage/025_jetbrains_plugin) for usage details.
\ No newline at end of file
diff --git a/docs/02-usage/020_running.md b/docs/02-usage/020_running.md
index ede78ec2..10dce262 100644
--- a/docs/02-usage/020_running.md
+++ b/docs/02-usage/020_running.md
@@ -2,14 +2,20 @@
Serena is a command-line tool with a variety of sub-commands.
This section describes
- * varies ways of running Serena
+ * various ways of running Serena
* how to run and configure the most important command, i.e. starting the MCP server
* other useful commands.
## Ways of Running Serena
In the following, we will refer to the command used to run Serena as ``,
-which you should replace with the appropriate command based on your chosen method.
+which you should replace with the appropriate command based on your chosen method,
+as detailed below.
+
+In general, to get help, append `--help` to the command, i.e.
+
+ --help
+ --help
### Using uvx
@@ -47,28 +53,30 @@ As a consequence, you will need to specify paths when using CLI commands that wo
:::
(docker)=
-### Using Docker (Experimental)
+### Using Docker
-:::{warning}
-Docker support is currently experimental with several limitations.
-:::
+The Docker approach offers several advantages:
-You can run the Serena MCP server directly via docker as follows,
+* better security isolation for shell command execution
+* no need to install language servers and dependencies locally
+* consistent environment across different systems
+
+You can run the Serena MCP server directly via Docker as follows,
assuming that the projects you want to work on are all located in `/path/to/your/projects`:
```shell
docker run --rm -i --network host -v /path/to/your/projects:/workspaces/projects ghcr.io/oraios/serena:latest serena
```
-Replace `/path/to/your/projects` with the absolute path to your projects directory. The Docker approach provides:
+This command mounts your projects into the container under `/workspaces/projects`, so when working with projects,
+you need to refer to them using the respective path (e.g. `/workspaces/projects/my-project`).
-* Better security isolation for shell command execution
-* No need to install language servers and dependencies locally
-* Consistent environment across different systems
+Alternatively, you may use Docker compose with the `compose.yml` file provided in the repository.
+See our [advanced Docker usage](https://github.com/oraios/serena/blob/main/DOCKER.md) documentation for more detailed instructions, configuration options, and limitations.
-Alternatively, use docker compose with the `compose.yml` file provided in the repository.
-
-See our [Docker Setup](https://github.com/oraios/serena/blob/main/DOCKER.md) documentation for more detailed setup instructions, configuration options, and known limitations.
+:::{note}
+Docker usage is subject to limitations; see the [advanced Docker usage](https://github.com/oraios/serena/blob/main/DOCKER.md) documentation for details.
+:::
### Using Nix
@@ -90,6 +98,12 @@ Given your preferred method of running Serena, you can start the MCP server usin
Note that no matter how you run the MCP server, Serena will, by default, start a web-based dashboard on localhost that will allow you to inspect
the server's operations, logs, and configuration.
+:::{tip}
+By default, Serena will use language servers for code understanding and analysis.
+With the [Serena JetBrains Plugin](025_jetbrains_plugin), we recently introduced a powerful alternative,
+which has several advantages over the language server-based approach.
+:::
+
### Standard I/O Mode
The typical usage involves the client (e.g. Claude Code, Codex or Cursor) running
@@ -148,6 +162,11 @@ to get a list of all available options.
Some useful options include:
* `--project `: specify the project to work on by name or path.
+ * `--project-from-cwd`: auto-detect the project from current working directory
+ (looking for a directory containing `.serena/project.yml` or `.git` in parent directories, activating the current directory if none is found);
+ This option is intended for CLI-based agents like Claude Code, Gemini and Codex, which are typically started from within the project directory
+ and which do not change directories during their operation.
+ * `--language-backend JetBrains`: use the Serena JetBrains Plugin as the language backend (overriding the default backend configured in the central configuration)
* `--context `: specify the operation [context](contexts) in which Serena shall operate
* `--mode `: specify one or more [modes](modes) to enable (can be passed several times)
* `--enable-web-dashboard `: enable or disable the web dashboard (enabled by default)
@@ -225,4 +244,4 @@ Here are some examples of commands you might find useful:
prompts edit-override prompt-name
```
-Explore the full set of commands and options using the CLI itself!
\ No newline at end of file
+Explore the full set of commands and options using the CLI itself!
diff --git a/docs/02-usage/025_jetbrains_plugin.md b/docs/02-usage/025_jetbrains_plugin.md
new file mode 100644
index 00000000..52343d91
--- /dev/null
+++ b/docs/02-usage/025_jetbrains_plugin.md
@@ -0,0 +1,56 @@
+# The Serena JetBrains Plugin
+
+The [JetBrains Plugin](https://plugins.jetbrains.com/plugin/28946-serena/) allows Serena to
+leverage the powerful code analysis and editing capabilities of your JetBrains IDE.
+
+```{raw} html
+
+
+
+
+
+```
+
+We recommend the JetBrains plugin as the preferred way of using Serena,
+especially for users of JetBrains IDEs.
+
+**Purchasing the JetBrains Plugin supports the Serena project.**
+The proceeds from plugin sales allow us to dedicate more resources to further developing and improving Serena.
+
+## Configuring Serena
+
+After installing the plugin, you need to configure Serena to use it.
+
+**Central Configuration**.
+Edit the global Serena configuration file located at `~/.serena/serena_config.yml`
+(`%USERPROFILE%\.serena\config.yaml` on Windows).
+Change the `language_backend` setting as follows:
+
+```yaml
+language_backend: JetBrains
+```
+
+**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
+launching the Serena MCP server.
+
+## Advantages of the JetBrains Plugin
+
+There are multiple features that are only available when using the JetBrains plugin:
+
+* **External library indexing**: Dependencies and libraries are fully indexed and accessible to Serena
+* **No additional setup**: No need to download or configure separate language servers
+* **Enhanced performance**: Faster tool execution thanks to optimized IDE integration
+* **Multi-language excellence**: First-class support for polyglot projects with multiple languages and frameworks
+
+We are also working on additional features like a `move_symbol` tool and debugging-related capabilities that
+will be available exclusively through the JetBrains plugin.
+
+## Usage with Other Editors
+
+We realize that not everyone uses a JetBrains IDE as their main code editor.
+You can still take advantage of the JetBrains plugin by running a JetBrains IDE instance alongside your
+preferred editor. Most JetBrains IDEs have a free community edition that you can use for this purpose.
+You just need to make sure that the project you are working on is open and indexed in the JetBrains IDE,
+so that Serena can connect to it.
diff --git a/docs/02-usage/030_clients.md b/docs/02-usage/030_clients.md
index a323f3b8..b757934b 100644
--- a/docs/02-usage/030_clients.md
+++ b/docs/02-usage/030_clients.md
@@ -1,10 +1,75 @@
# Connecting Your MCP Client
-In the following, we provide default configurations for popular MCP-enabled clients.
+In the following, we provide general instructions on how to connect Serena to your MCP-enabled client,
+as well as specific instructions for popular clients.
-Depending on your needs, you might want to customize Serena's behaviour by
- * [adding command-line arguments](mcp-args)
- * [adjusting configuration](050_configuration).
+:::{note}
+The configurations we provide for particular clients below will run the latest version of Serena
+using the `stdio` protocol with `uvx`.
+Adapt the commands to your preferred way of [running Serena](020_running), adding any additional
+command-line arguments as needed.
+:::
+
+(clients-general-instructions)=
+## General Instructions
+
+In general, Serena can be used with any MCP-enabled client.
+To connect Serena to your favourite client, simply
+
+1. determine how to add a custom MCP server to your client (refer to the client's documentation).
+2. add a new MCP server entry by specifying either
+ * a [run command](start-mcp-server) that allows the client to start the MCP server in stdio mode as a subprocess, or
+ * the URL of the HTTP/SSE endpoint, having started the [Serena MCP server in HTTP/SSE mode](streamable-http) beforehand.
+
+Find concrete examples for popular clients below.
+
+Depending on your needs, you might want to further customize Serena's behaviour by
+* [adding command-line arguments](mcp-args)
+* [adjusting configuration](050_configuration).
+
+**Mode of Operation**.
+Note that some clients have a per-workspace MCP configuration (e.g, VSCode and Claude Code),
+while others have a global MCP configuration (e.g. Codex and Claude Desktop).
+
+- In the per-workspace case, you typically want to start Serena with your workspace directory as the project directory
+ and never switch to a different project. This is achieved by specifying the
+ `--project ` argument with a single-project [context](#contexts) (e.g. `ide` or `claude-code`).
+- In the global configuration case, you must first activate the project you want to work on, which you can do by asking
+ the LLM to do so (e.g., "Activate the current dir as project using serena"). In such settings, the `activate_project`
+ tool is required.
+
+**Tool Selection**.
+While you may be able to turn off tools through your client's interface (e.g., in VSCode or Claude Desktop),
+we recommend selecting your base tool set through Serena's configuration, as Serena's prompts automatically
+adjust based on which tools are enabled/disabled.
+A key mechanism for this is to use the appropriate [context](#contexts) when starting Serena.
+
+(clients-common-pitfalls)=
+### Common Pitfalls
+
+**Escaping Paths Correctly**.
+Note that if your client configuration uses JSON, special characters (like backslashes) need to be escaped properly.
+In particular, if you are specifying paths containing backslashes on Windows
+(note that you can also just use forward slashes), be sure to escape them correctly (`\\`).
+
+**Discoverability of `uvx`**.
+Your client may not find the `uvx` command, even if it is on your system PATH.
+In this case, a workaround is to provide the full path to the `uvx` executable.
+
+**Environment Variables**.
+Some language servers may require additional environment variables to be set (e.g. F# on macOS with Homebrew),
+which you may need to explicitly add to the MCP server configuration.
+Note that for some clients (e.g. Claude Desktop), the spawned MCP server process may not inherit environment variables that
+are only configured in your shell profile (e.g. `.bashrc`, `.zshrc`, etc.); they would need to be set system-wide instead.
+An easy fix is to add them explicitly to the MCP server entry.
+For example, in Claude Desktop and other clients, you can simply add an `env` key to the `serena`
+object, e.g.
+
+```
+"env": {
+ "DOTNET_ROOT": "/opt/homebrew/Cellar/dotnet/9.0.8/libexec"
+}
+```
## Claude Code
@@ -13,25 +78,57 @@ 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,
```shell
-claude mcp add serena -- start-mcp-server --context ide-assistant --project "$(pwd)"
-```
-
-where `` is [your way of running Serena](020_running).
-For example, when using `uvx`, the above command becomes
-
-```shell
-claude mcp add serena -- uvx --from git+https://github.com/oraios/serena serena start-mcp-server --context ide-assistant --project "$(pwd)"
+claude mcp add serena -- uvx --from git+https://github.com/oraios/serena serena start-mcp-server --context claude-code --project "$(pwd)"
```
Note:
- * We use the `ide-assistant` context to disable unnecessary tools (avoiding duplication
+ * We use the `claude-code` context to disable unnecessary tools (avoiding duplication
with Claude Code's built-in capabilities).
* We specify the current directory as the project directory with `--project "$(pwd)"`, such
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:
+
+```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.
+
Be sure to use at least `v1.0.52` of Claude Code (as earlier versions do not read MCP server system prompts upon startup).
+## VSCode
+
+While serena can be directly installed from the GitHub MCP server registry, we recommend to set it up manually
+(at least for now, until the configuration there has been improved). Just paste the following into
+`/.vscode/mcp.json`, or edit the entry after using the option `install into workspace`:
+
+```json
+{
+ "servers": {
+ "oraios/serena": {
+ "type": "stdio",
+ "command": "uvx",
+ "args": [
+ "--from",
+ "git+https://github.com/oraios/serena",
+ "serena",
+ "start-mcp-server",
+ "--context",
+ "ide",
+ "--project",
+ "${workspaceFolder}"
+ ]
+ }
+ },
+ "inputs": []
+}
+```
+
## Codex
Serena works with OpenAI's Codex CLI out of the box, but you have to use the `codex` context for it to work properly. (The technical reason is that Codex doesn't fully support the MCP specifications, so some massaging of tools is required.).
@@ -45,6 +142,7 @@ For example, when using `uvx`, add the following section:
command = "uvx"
args = ["--from", "git+https://github.com/oraios/serena", "serena", "start-mcp-server", "--context", "codex"]
```
+
After codex has started, you need to activate the project, which you can do by saying:
"Activate the current dir as project using serena"
@@ -61,77 +159,132 @@ that was already taken).
## Claude Desktop
-On Windows and macOS there are official [Claude Desktop applications by Anthropic](https://claude.ai/download), for Linux there is an [open-source
+On Windows and macOS, there are official [Claude Desktop applications by Anthropic](https://claude.ai/download); for Linux, there is an [open-source
community version](https://github.com/aaddrick/claude-desktop-debian).
To configure MCP server settings, go to File / Settings / Developer / MCP Servers / Edit Config,
which will let you open the JSON file `claude_desktop_config.json`.
-Add the `serena` MCP server configuration, using a [run command](020_running.md) depending on your setup.
+Add the `serena` MCP server configuration
-* local installation:
-
- ```json
- {
- "mcpServers": {
- "serena": {
- "command": "/abs/path/to/uv",
- "args": ["run", "--directory", "/abs/path/to/serena", "serena", "start-mcp-server"]
- }
- }
- }
- ```
-
-* uvx:
-
- ```json
- {
- "mcpServers": {
- "serena": {
- "command": "/abs/path/to/uvx",
- "args": ["--from", "git+https://github.com/oraios/serena", "serena", "start-mcp-server"]
- }
- }
+```json
+{
+ "mcpServers": {
+ "serena": {
+ "command": "uvx",
+ "args": [
+ "--from",
+ "git+https://github.com/oraios/serena",
+ "serena",
+ "start-mcp-server"
+ ]
+ }
}
- ```
+}
+```
-* docker:
+If your language server requires specific environment variables to be set (e.g. F# on macOS with Homebrew),
+you can add them via an `env` key (see [above](#clients-common-pitfalls)).
- ```json
- {
- "mcpServers": {
- "serena": {
- "command": "docker",
- "args": ["run", "--rm", "-i", "--network", "host", "-v", "/path/to/your/projects:/workspaces/projects", "ghcr.io/oraios/serena:latest", "serena", "start-mcp-server", "--transport", "stdio"]
- }
- }
- }
- ```
-
-If you are using paths containing backslashes for paths on Windows
-(note that you can also just use forward slashes), be sure to escape them correctly (`\\`).
-
-That's it! Save the config and then restart Claude Desktop. You are ready for activating your first project.
+Once you have created the new MCP server entry, save the config and then restart Claude Desktop.
:::{attention}
-Be sure to fully quit the Claude Desktop application via File / Exit, as regularly closing the application will just minimize it.
+Be sure to fully quit the Claude Desktop application via File / Exit, as regularly closing the application will just
+minimize it.
:::
After restarting, you should see Serena's tools in your chat interface (notice the small hammer icon).
-For more information on MCP servers with Claude Desktop, see [the official quick start guide](https://modelcontextprotocol.io/quickstart/user).
+For more information on MCP servers with Claude Desktop,
+see [the official quick start guide](https://modelcontextprotocol.io/quickstart/user).
+
+## JetBrains Junie
+
+Open Junie, go to the three dots in the top right corner, then Settings / MCP Settings and add Serena to Junie's global
+MCP server configuration:
+
+```json
+{
+ "mcpServers": {
+ "serena": {
+ "command": "uvx",
+ "args": [
+ "--from",
+ "git+https://github.com/oraios/serena",
+ "serena",
+ "start-mcp-server",
+ "--context",
+ "ide"
+ ]
+ }
+ }
+}
+```
+
+You will have to prompt Junie to "Activate the current project using serena's activation tool" at the
+start of each session.
+
+## JetBrains AI Assistant
+
+Here you can set up the more convenient per-project MCP server configuration, as the AI assistant supports specifying
+the launch working directory.
+
+Go to Settings / Tools / AI Assistant / MCP and add a new **local** configuration via the `as JSON` option:
+
+```json
+{
+ "mcpServers": {
+ "serena": {
+ "command": "uvx",
+ "args": [
+ "--from",
+ "git+https://github.com/oraios/serena",
+ "serena",
+ "start-mcp-server",
+ "--context",
+ "ide",
+ "--project",
+ "$(pwd)"
+ ]
+ }
+ }
+}
+```
+
+Then make sure to configure the working directory to be the project root.
+
+## Antigravity
+
+:::{warning}
+At the time of writing (12/2025), Antigravity does not seem to work with Serena due to schema validation issues
+which are beyond our control.
+The client starts Serena but then crashes with `[internal] marshal message: string field contains invalid UTF-8`.
+Nevertheless, we provide a configuration that should work once the issue is resolved.
+:::
+
+Add this configuration:
+
+```json
+{
+ "mcpServers": {
+ "serena": {
+ "command": "uvx",
+ "args": [
+ "--from",
+ "git+https://github.com/oraios/serena",
+ "serena",
+ "start-mcp-server",
+ "--context",
+ "ide"
+ ]
+ }
+ }
+}
+```
## Other Clients
-In general, Serena can be used with any MCP-enabled client.
-To connect Serena to your favourite client, simply
-
-1. determine how to add a custom MCP server to your client (refer to the client's documentation).
-2. add a new MCP server entry by specifying either
- * a [run command](start-mcp-server) that allows the client to start the MCP server in stdio mode as a subprocess, or
- * the URL of the HTTP/SSE endpoint, having started the [Serena MCP server in HTTP/SSE mode](streamable-http) beforehand.
-
-Below, we list some popular categories of clients that Serena can be used with.
+For other clients, follow the [general instructions](#clients-general-instructions) above to set up Serena as an MCP server.
### Terminal-Based Clients
@@ -146,12 +299,15 @@ There are many terminal-based coding assistants that support MCP servers, such a
They generally benefit from the symbolic tools provided by Serena. You might want to customize some aspects of Serena
by writing your own context, modes or prompts to adjust it to the client's respective internal capabilities (and your general workflow).
+In most cases, the `ide` context is likely to be appropriate for such clients, i.e. add the arguments `--context ide`
+in order to reduce tool duplication.
+
### MCP-Enabled IDEs and Coding Clients (Cline, Roo-Code, Cursor, Windsurf, etc.)
Most of the popular existing coding assistants (e.g. IDE extensions) and AI-enabled IDEs themselves support connections
to MCP Servers. Serena generally boosts performance by providing efficient tools for symbolic operations.
-We generally **recommend to use the `ide-assistant` context** for these integrations by adding the arguments `--context ide-assistant`
+We generally recommend to use the `ide` context for these integrations by adding the arguments `--context ide`
in order to reduce tool duplication.
### Local GUIs and Agent Frameworks
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
diff --git a/docs/02-usage/050_configuration.md b/docs/02-usage/050_configuration.md
index 341c53d7..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.
@@ -44,9 +45,22 @@ A context is set at startup when launching Serena (e.g., via CLI options for an
Serena comes with pre-defined contexts:
* `desktop-app`: Tailored for use with desktop applications like Claude Desktop. This is the default.
+ The full set of Serena's tools is provided, as the application is assumed to have no prior coding-specific capabilities.
+* `claude-code`: Optimized for use with Claude Code, it disables tools that would duplicate Claude Code's built-in capabilities.
+* `codex`: Optimized for use with OpenAI Codex.
+* `ide`: Generic context for IDE assistants/coding agents, e.g. VSCode, Cursor, or Cline, focusing on augmenting existing capabilities.
+ Basic file operations and shell execution are assumed to be handled by the assistant's own capabilities.
* `agent`: Designed for scenarios where Serena acts as a more autonomous agent, for example, when used with Agno.
-* `ide-assistant`: Optimized for integration into IDEs like VSCode, Cursor, or Cline, focusing on in-editor coding assistance.
- Choose the context that best matches the type of integration you are using.
+
+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.
@@ -74,18 +88,34 @@ Examples of built-in modes include:
* `editing`: Optimizes Serena for direct code modification tasks.
* `interactive`: Suitable for a conversational, back-and-forth interaction style.
* `one-shot`: Configures Serena for tasks that should be completed in a single response, often used with `planning` for generating reports or initial plans.
-* `no-onboarding`: Skips the initial onboarding process if it's not needed for a particular session.
-* `onboarding`: (Usually triggered automatically) Focuses on the project onboarding process.
+* `no-onboarding`: Skips the initial onboarding process if it's not needed for a particular session but retains the memory tools (assuming initial memories were created externally).
+* `onboarding`: Focuses on the project onboarding process.
+* `no-memories`: Disables all memory tools (and tools building on memories such as onboarding tools)
+
+Find the concrete definitions of these modes [here](https://github.com/oraios/serena/tree/main/src/serena/resources/config/modes).
+
+:::{important}
+By default, Serena activates the two modes `interactive` and `editing`.
+
+As soon as you start to specify modes, 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
+```
+:::
Modes can be set at startup (similar to contexts) but can also be _switched dynamically_ during a session.
You can instruct the LLM to use the `switch_modes` tool to activate a different set of modes (e.g., "Switch to planning and one-shot modes").
When launching Serena, specify modes using `--mode `; multiple modes can be specified, e.g. `--mode planning --mode no-onboarding`.
-:warning: **Mode Compatibility**: While you can combine modes, some may be semantically incompatible (e.g., `interactive` and `one-shot`).
+:::{note}
+**Mode Compatibility**: While you can combine modes, some may be semantically incompatible (e.g., `interactive` and `one-shot`).
Serena currently does not prevent incompatible combinations; it is up to the user to choose sensible mode configurations.
+:::
-You can manage contexts using the `mode` command,
+You can manage modes using the `mode` command,
mode --help
mode list
@@ -93,4 +123,31 @@ You can manage contexts using the `mode` command,
mode edit
mode delete
-where `` is [your way of running Serena](020_running).
\ No newline at end of file
+where `` is [your way of running Serena](020_running).
+
+## Advanced Configuration
+
+For advanced users, Serena's configuration can be further customized.
+
+### Serena Data Directory
+
+The Serena user data directory (where configuration, language server files, logs, etc. are stored) defaults to `~/.serena`.
+You can change this location by setting the `SERENA_HOME` environment variable to your desired path.
+
+### Custom Prompts
+
+All of Serena's prompts can be fully customized.
+We define prompt as jinja templates in yaml files, and you can inspect our default prompts [here](https://github.com/oraios/serena/tree/main/src/serena/resources/config/prompt_templates).
+
+To override a prompt, simply add a .yml file to the `prompt_templates` folder in your Serena data directory
+which defines the prompt with the same name as the default prompt you want to override.
+For example, to override the `system_prompt`, you could create a file `~/.serena/prompt_templates/system_prompt.yml` (assuming default Serena data folder location)
+with content like:
+
+```yaml
+prompts:
+ system_prompt: |
+ Whatever you want ...
+```
+
+It is advisable to use the default prompt as a starting point and modify it to suit your needs.
diff --git a/docs/03-special-guides/groovy_setup_guide_for_serena.md b/docs/03-special-guides/groovy_setup_guide_for_serena.md
new file mode 100644
index 00000000..50e5be23
--- /dev/null
+++ b/docs/03-special-guides/groovy_setup_guide_for_serena.md
@@ -0,0 +1,88 @@
+# Groovy Setup Guide for Serena
+
+The Groovy support in Serena is incomplete and requires the user to provide a functioning,
+JVM-based Groovy language server as a jar. This intermediate state allows the contributors
+of Groovy support to use Serena internally and hopefully to accelerate their open-source
+release of a Groovy language server in the future.
+
+If you happen to have a Groovy language server JAR file, you can configure Serena to use it
+by following the instructions below.
+
+---
+## Prerequisites
+
+- Groovy Language Server JAR file
+ - Can be any open-source Groovy language server or your custom implementation
+ - The JAR must be compatible with standard LSP protocol
+
+---
+## Configuration
+
+Configure Groovy Language Server by adding settings to your `~/.serena/serena_config.yml`:
+
+### Basic Configuration
+
+```yaml
+ls_specific_settings:
+ groovy:
+ ls_jar_path: '/path/to/groovy-language-server.jar'
+ ls_jar_options: '-Xmx2G -Xms512m'
+```
+
+### Custom Java Paths
+
+If you have specific Java installations:
+
+```yaml
+ls_specific_settings:
+ groovy:
+ ls_jar_path: '/path/to/groovy-language-server.jar'
+ ls_java_home_path: '/usr/lib/jvm/java-21-openjdk' # Custom JAVA_HOME directory
+ ls_jar_options: '-Xmx2G -Xms512m' # Optional JVM options
+```
+
+### Configuration Options
+
+- `ls_jar_path`: Absolute path to your Groovy Language Server JAR file (required)
+- `ls_java_home_path`: Custom JAVA_HOME directory for Java installation (optional)
+ - When specified, Serena will use this Java installation instead of downloading bundled Java
+ - Java executable path is automatically determined based on platform:
+ - Windows: `{ls_java_home_path}/bin/java.exe`
+ - Linux/macOS: `{ls_java_home_path}/bin/java`
+ - Validates that Java executable exists at the expected location
+- `ls_jar_options`: JVM options for language server (optional)
+ - Common options:
+ - `-Xmx`: Maximum heap size (e.g., `-Xmx2G` for 2GB)
+ - `-Xms`: Initial heap size (e.g., `-Xms512m` for 512MB)
+
+---
+## Project Structure Requirements
+
+For optimal Groovy Language Server performance, ensure your project follows standard Groovy/Gradle structure:
+
+```
+project-root/
+├── src/
+│ ├── main/
+│ │ ├── groovy/
+│ │ └── resources/
+│ └── test/
+│ ├── groovy/
+│ └── resources/
+├── build.gradle or build.gradle.kts
+├── settings.gradle or settings.gradle.kts
+└── gradle/
+ └── wrapper/
+```
+
+---
+## Using Serena with Groovy
+
+- Serena automatically detects Groovy files (`*.groovy`, `*.gvy`) and will start a Groovy Language Server JAR process per project when needed.
+- Optimal results require that your project compiles successfully via Gradle or Maven. If compilation fails, fix build errors in your build tool first.
+
+## Reference
+
+- **Groovy Documentation**: [https://groovy-lang.org/documentation.html](https://groovy-lang.org/documentation.html)
+- **Gradle Documentation**: [https://docs.gradle.org](https://docs.gradle.org)
+- **Serena Configuration**: [../02-usage/050_configuration.md](../02-usage/050_configuration.md)
\ No newline at end of file
diff --git a/docs/_config.yml b/docs/_config.yml
index dbea0794..89a0f6c2 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -53,7 +53,7 @@ parse:
#######################################################################################
# HTML-specific settings
html:
- #favicon : # A path to a favicon image
+ favicon : "../src/serena/resources/dashboard/serena-icon-32.png"
use_edit_page_button : false # Whether to add an "edit this page" button to pages. If `true`, repository information in repository: must be filled in
use_repository_button : false # Whether to add a link to your repository button
use_issues_button : false # Whether to add an "open an issue" button
diff --git a/docs/_static/images/jetbrains-marketplace-button.png b/docs/_static/images/jetbrains-marketplace-button.png
new file mode 100644
index 00000000..fd62f400
Binary files /dev/null and b/docs/_static/images/jetbrains-marketplace-button.png differ
diff --git a/public/.gitignore b/public/.gitignore
deleted file mode 100644
index e69de29b..00000000
diff --git a/pyproject.toml b/pyproject.toml
index 618d1b3f..7c44e907 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -19,7 +19,7 @@ dependencies = [
"fortls>=3.2.2",
"overrides>=7.7.0,<8",
"python-dotenv>=1.0.0, <2",
- "mcp==1.12.3",
+ "mcp==1.23.0",
"flask>=3.0.0",
"sensai-utils>=1.5.0",
"pydantic>=2.10.6",
@@ -72,6 +72,7 @@ dev = [
"jupyter-book>=1,<2",
"nbsphinx",
"pyinstrument",
+ "pytest-timeout>=2.4.0",
]
agno = ["agno>=2.2.1", "sqlalchemy>=2.0.40"]
google = ["google-genai>=1.8.0"]
@@ -121,7 +122,7 @@ PYDEVD_DISABLE_FILE_VALIDATION = "1"
# Uses PYTEST_MARKERS env var for default markers
# For custom markers, one can either adjust the env var or just use -m option in the command line,
# as the second -m option will override the first one.
-test = "pytest test -vv -m \"${PYTEST_MARKERS:-not rust and not erlang}\""
+test = "pytest test -vv"
_black_check = "black --check src scripts test"
_ruff_check = "ruff check src scripts test"
_black_format = "black src scripts test"
@@ -273,8 +274,10 @@ markers = [
"go: language server running for Go",
"java: language server running for Java",
"kotlin: language server running for kotlin",
+ "groovy: language server running for Groovy",
"rust: language server running for Rust",
"typescript: language server running for TypeScript",
+ "vue: language server running for Vue (uses TypeScript LSP)",
"php: language server running for PHP",
"perl: language server running for Perl",
"csharp: language server running for C#",
@@ -293,12 +296,16 @@ markers = [
"erlang: language server running for Erlang",
"scala: language server running for Scala",
"al: language server running for AL (Microsoft Dynamics 365 Business Central)",
+ "fsharp: language server running for F#",
"rego: language server running for Rego",
"markdown: language server running for Markdown",
"julia: Julia language server tests",
"fortran: language server running for Fortran",
"haskell: Haskell language server tests",
"yaml: language server running for YAML",
+ "powershell: language server running for PowerShell",
+ "slow: tests that require additional Expert instances and have long startup times (~60-90s each)",
+ "toml: language server running for TOML",
]
[tool.codespell]
@@ -306,4 +313,4 @@ markers = [
skip = '.git*,*.svg,*.lock,*.min.*'
check-hidden = true
# ignore-regex = ''
-# ignore-words-list = ''
+ignore-words-list = 'paket'
\ No newline at end of file
diff --git a/resources/jetbrains-marketplace-button.cdr b/resources/jetbrains-marketplace-button.cdr
new file mode 100644
index 00000000..9c5daae9
Binary files /dev/null and b/resources/jetbrains-marketplace-button.cdr differ
diff --git a/resources/vscode_sponsor_logo.png b/resources/vscode_sponsor_logo.png
index ebd8f317..5f253cfd 100644
Binary files a/resources/vscode_sponsor_logo.png and b/resources/vscode_sponsor_logo.png differ
diff --git a/scripts/profile_tool_call.py b/scripts/profile_tool_call.py
index e0e69981..8fe922a5 100644
--- a/scripts/profile_tool_call.py
+++ b/scripts/profile_tool_call.py
@@ -25,7 +25,6 @@ if __name__ == "__main__":
serena_config = SerenaConfig.from_config_file()
serena_config.log_level = logging.INFO
- serena_config.jetbrains = False
serena_config.gui_log_window_enabled = False
serena_config.web_dashboard = False
diff --git a/src/serena/agent.py b/src/serena/agent.py
index 70d5bb73..02d44079 100644
--- a/src/serena/agent.py
+++ b/src/serena/agent.py
@@ -18,13 +18,14 @@ 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 SerenaConfig, ToolInclusionDefinition
+from serena.config.serena_config import LanguageBackend, SerenaConfig, ToolInclusionDefinition
from serena.dashboard import SerenaDashboardAPI
from serena.ls_manager import LanguageServerManager
from serena.project import Project
from serena.prompt_factory import SerenaPromptFactory
from serena.task_executor import TaskExecutor
from serena.tools import ActivateProjectTool, GetCurrentConfigTool, ReplaceContentTool, Tool, ToolMarker, ToolRegistry
+from serena.util.gui import system_has_usable_display
from serena.util.inspection import iter_subclasses
from serena.util.logging import MemoryLogHandler
from solidlsp.ls_config import Language
@@ -204,6 +205,7 @@ class SerenaAgent:
# open GUI log window if enabled
self._gui_log_viewer: Optional["GuiLogViewer"] = None
if self.serena_config.gui_log_window_enabled:
+ log.info("Opening GUI window")
if platform.system() == "Darwin":
log.warning("GUI log window is not supported on macOS")
else:
@@ -213,6 +215,8 @@ class SerenaAgent:
self._gui_log_viewer = GuiLogViewer("dashboard", title="Serena Logs", memory_log_handler=get_memory_log_handler())
self._gui_log_viewer.start()
+ else:
+ log.debug("GUI window is disabled")
# set the agent context
if context is None:
@@ -232,7 +236,10 @@ class SerenaAgent:
self._tool_usage_stats = ToolUsageStats(token_count_estimator)
# log fundamental information
- log.info(f"Starting Serena server (version={serena_version()}, process id={os.getpid()}, parent process id={os.getppid()})")
+ log.info(
+ f"Starting Serena server (version={serena_version()}, process id={os.getpid()}, parent process id={os.getppid()}; "
+ f"language backend={self.serena_config.language_backend.name})"
+ )
log.info("Configuration file: %s", self.serena_config.config_file_path)
log.info("Available projects: {}".format(", ".join(self.serena_config.project_names)))
log.info(f"Loaded tools ({len(self._all_tools)}): {', '.join([tool.get_name_from_cls() for tool in self._all_tools.values()])}")
@@ -244,7 +251,7 @@ class SerenaAgent:
tool_inclusion_definitions: list[ToolInclusionDefinition] = [self.serena_config, self._context]
if self._context.single_project:
tool_inclusion_definitions.extend(self._single_project_context_tool_inclusion_definitions(project))
- if self.serena_config.jetbrains:
+ if self.serena_config.language_backend == LanguageBackend.JETBRAINS:
tool_inclusion_definitions.append(SerenaAgentMode.from_name_internal("jetbrains"))
self._base_tool_set = ToolSet.default().apply(*tool_inclusion_definitions)
@@ -280,15 +287,21 @@ class SerenaAgent:
if self.serena_config.web_dashboard:
self._dashboard_thread, port = SerenaDashboardAPI(
get_memory_log_handler(), tool_names, agent=self, tool_usage_stats=self._tool_usage_stats
- ).run_in_thread()
- dashboard_url = f"http://127.0.0.1:{port}/dashboard/index.html"
+ ).run_in_thread(host=self.serena_config.web_dashboard_listen_address)
+ dashboard_host = self.serena_config.web_dashboard_listen_address
+ if dashboard_host == "0.0.0.0":
+ dashboard_host = "localhost"
+ dashboard_url = f"http://{dashboard_host}:{port}/dashboard/index.html"
log.info("Serena web dashboard started at %s", dashboard_url)
if self.serena_config.web_dashboard_open_on_launch:
- # open the dashboard URL in the default web browser (using a separate process to control
- # output redirection)
- process = multiprocessing.Process(target=self._open_dashboard, args=(dashboard_url,))
- process.start()
- process.join(timeout=1)
+ if not system_has_usable_display():
+ log.warning("Not opening the Serena web dashboard automatically because no usable display was detected.")
+ else:
+ # open the dashboard URL in the default web browser (using a separate process to control
+ # output redirection)
+ process = multiprocessing.Process(target=self._open_dashboard, args=(dashboard_url,))
+ process.start()
+ process.join(timeout=1)
# inform the GUI window (if any)
if self._gui_log_viewer is not None:
self._gui_log_viewer.set_dashboard_url(dashboard_url)
@@ -517,7 +530,7 @@ class SerenaAgent:
"""
:return: whether this agent uses language server-based code analysis
"""
- return not self.serena_config.jetbrains
+ return self.serena_config.language_backend == LanguageBackend.LSP
def _activate_project(self, project: Project) -> None:
log.info(f"Activating {project.project_name} at {project.project_root}")
@@ -684,18 +697,22 @@ class SerenaAgent:
ToolRegistry().print_tool_overview(self._active_tools.values())
def __del__(self) -> None:
+ self.shutdown()
+
+ def shutdown(self, timeout: float = 2.0) -> None:
"""
- Destructor to clean up the language server instance and GUI logger
+ Shuts down the agent, freeing resources and stopping background tasks.
"""
if not hasattr(self, "_is_initialized"):
return
log.info("SerenaAgent is shutting down ...")
if self._active_project is not None:
- self._active_project.shutdown()
+ self._active_project.shutdown(timeout=timeout)
self._active_project = None
if self._gui_log_viewer:
log.info("Stopping the GUI log window ...")
self._gui_log_viewer.stop()
+ self._gui_log_viewer = None
def get_tool_by_name(self, tool_name: str) -> Tool:
tool_class = ToolRegistry().get_tool_class_by_name(tool_name)
diff --git a/src/serena/cli.py b/src/serena/cli.py
index dc47d9b4..bc4af3e6 100644
--- a/src/serena/cli.py
+++ b/src/serena/cli.py
@@ -5,6 +5,7 @@ import os
import shutil
import subprocess
import sys
+from collections.abc import Iterator
from logging import Logger
from pathlib import Path
from typing import Any, Literal
@@ -17,18 +18,14 @@ from tqdm import tqdm
from serena.agent import SerenaAgent
from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode
-from serena.config.serena_config import ProjectConfig, SerenaConfig, SerenaPaths
+from serena.config.serena_config import LanguageBackend, ProjectConfig, SerenaConfig, SerenaPaths
from serena.constants import (
DEFAULT_CONTEXT,
DEFAULT_MODES,
- PROMPT_TEMPLATES_DIR_IN_USER_HOME,
PROMPT_TEMPLATES_DIR_INTERNAL,
SERENA_LOG_FORMAT,
- SERENA_MANAGED_DIR_IN_HOME,
SERENAS_OWN_CONTEXT_YAMLS_DIR,
SERENAS_OWN_MODE_YAMLS_DIR,
- USER_CONTEXT_YAMLS_DIR,
- USER_MODE_YAMLS_DIR,
)
from serena.mcp import SerenaMCPFactory, SerenaMCPFactorySingleProcess
from serena.project import Project
@@ -39,6 +36,42 @@ from solidlsp.util.subprocess_util import subprocess_kwargs
log = logging.getLogger(__name__)
+
+def find_project_root(root: str | Path | None = None) -> str:
+ """Find project root by walking up from CWD.
+
+ Checks for .serena/project.yml first (explicit Serena project), then .git (git root).
+ Falls back to CWD if no marker is found.
+
+ :param root: If provided, constrains the search to this directory and below
+ (acts as a virtual filesystem root). Search stops at this boundary.
+ :return: absolute path to project root (falls back to CWD if no marker found)
+ """
+ current = Path.cwd().resolve()
+ boundary = Path(root).resolve() if root is not None else None
+
+ def ancestors() -> Iterator[Path]:
+ """Yield current directory and ancestors up to boundary."""
+ yield current
+ for parent in current.parents:
+ yield parent
+ if boundary is not None and parent == boundary:
+ return
+
+ # First pass: look for .serena
+ for directory in ancestors():
+ if (directory / ".serena" / "project.yml").is_file():
+ return str(directory)
+
+ # Second pass: look for .git
+ for directory in ancestors():
+ if (directory / ".git").exists(): # .git can be file (worktree) or dir
+ return str(directory)
+
+ # Fall back to CWD
+ return str(current)
+
+
# --------------------- Utilities -------------------------------------
@@ -121,6 +154,12 @@ class TopLevelCommands(AutoRegisteringGroup):
show_default=True,
help="Built-in mode names or paths to custom mode YAMLs.",
)
+ @click.option(
+ "--language-backend",
+ type=click.Choice([lb.value for lb in LanguageBackend]),
+ default=None,
+ help="Override the configured language backend.",
+ )
@click.option(
"--transport",
type=click.Choice(["stdio", "sse", "streamable-http"]),
@@ -128,8 +167,16 @@ class TopLevelCommands(AutoRegisteringGroup):
show_default=True,
help="Transport protocol.",
)
- @click.option("--host", type=str, default="0.0.0.0", show_default=True)
- @click.option("--port", type=int, default=8000, show_default=True)
+ @click.option(
+ "--host",
+ type=str,
+ default="0.0.0.0",
+ show_default=True,
+ help="Listen address for the MCP server (when using corresponding transport).",
+ )
+ @click.option(
+ "--port", type=int, default=8000, show_default=True, help="Listen port for the MCP server (when using corresponding transport)."
+ )
@click.option("--enable-web-dashboard", type=bool, is_flag=False, default=None, help="Override dashboard setting in config.")
@click.option("--enable-gui-log-window", type=bool, is_flag=False, default=None, help="Override GUI log window setting in config.")
@click.option(
@@ -140,11 +187,19 @@ class TopLevelCommands(AutoRegisteringGroup):
)
@click.option("--trace-lsp-communication", type=bool, is_flag=False, default=None, help="Whether to trace LSP communication.")
@click.option("--tool-timeout", type=float, default=None, help="Override tool execution timeout in config.")
+ @click.option(
+ "--project-from-cwd",
+ is_flag=True,
+ default=False,
+ help="Auto-detect project from current working directory (searches for .serena/project.yml or .git, falls back to CWD). Intended for CLI-based agents like Claude Code, Gemini and Codex.",
+ )
def start_mcp_server(
project: str | None,
project_file_arg: str | None,
+ project_from_cwd: bool | None,
context: str,
modes: tuple[str, ...],
+ language_backend: str | None,
transport: Literal["stdio", "sse", "streamable-http"],
host: str,
port: int,
@@ -173,12 +228,21 @@ class TopLevelCommands(AutoRegisteringGroup):
log.info("Initializing Serena MCP server")
log.info("Storing logs in %s", log_path)
+
+ # Handle --project-from-cwd flag
+ if project_from_cwd:
+ if project is not None or project_file_arg is not None:
+ raise click.UsageError("--project-from-cwd cannot be used with --project or positional project argument")
+ project = find_project_root()
+ 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)
server = factory.create_mcp_server(
host=host,
port=port,
modes=modes,
+ language_backend=LanguageBackend.from_str(language_backend) if language_backend else None,
enable_web_dashboard=enable_web_dashboard,
enable_gui_log_window=enable_gui_log_window,
log_level=log_level,
@@ -270,7 +334,7 @@ class ModeCommands(AutoRegisteringGroup):
if not (name or from_internal):
raise click.UsageError("Provide at least one of --name or --from-internal.")
mode_name = name or from_internal
- dest = os.path.join(USER_MODE_YAMLS_DIR, f"{mode_name}.yml")
+ dest = os.path.join(SerenaPaths().user_modes_dir, f"{mode_name}.yml")
src = (
os.path.join(SERENAS_OWN_MODE_YAMLS_DIR, f"{from_internal}.yml")
if from_internal
@@ -289,7 +353,7 @@ class ModeCommands(AutoRegisteringGroup):
@click.command("edit", help="Edit a custom mode YAML file.")
@click.argument("mode_name")
def edit(mode_name: str) -> None:
- path = os.path.join(USER_MODE_YAMLS_DIR, f"{mode_name}.yml")
+ path = os.path.join(SerenaPaths().user_modes_dir, f"{mode_name}.yml")
if not os.path.exists(path):
if mode_name in SerenaAgentMode.list_registered_mode_names(include_user_modes=False):
click.echo(
@@ -305,7 +369,7 @@ class ModeCommands(AutoRegisteringGroup):
@click.command("delete", help="Delete a custom mode file.")
@click.argument("mode_name")
def delete(mode_name: str) -> None:
- path = os.path.join(USER_MODE_YAMLS_DIR, f"{mode_name}.yml")
+ path = os.path.join(SerenaPaths().user_modes_dir, f"{mode_name}.yml")
if not os.path.exists(path):
click.echo(f"Custom mode '{mode_name}' not found.")
return
@@ -347,7 +411,7 @@ class ContextCommands(AutoRegisteringGroup):
if not (name or from_internal):
raise click.UsageError("Provide at least one of --name or --from-internal.")
ctx_name = name or from_internal
- dest = os.path.join(USER_CONTEXT_YAMLS_DIR, f"{ctx_name}.yml")
+ dest = os.path.join(SerenaPaths().user_contexts_dir, f"{ctx_name}.yml")
src = (
os.path.join(SERENAS_OWN_CONTEXT_YAMLS_DIR, f"{from_internal}.yml")
if from_internal
@@ -366,7 +430,7 @@ class ContextCommands(AutoRegisteringGroup):
@click.command("edit", help="Edit a custom context YAML file.")
@click.argument("context_name")
def edit(context_name: str) -> None:
- path = os.path.join(USER_CONTEXT_YAMLS_DIR, f"{context_name}.yml")
+ path = os.path.join(SerenaPaths().user_contexts_dir, f"{context_name}.yml")
if not os.path.exists(path):
if context_name in SerenaAgentContext.list_registered_context_names(include_user_contexts=False):
click.echo(
@@ -382,7 +446,7 @@ class ContextCommands(AutoRegisteringGroup):
@click.command("delete", help="Delete a custom context file.")
@click.argument("context_name")
def delete(context_name: str) -> None:
- path = os.path.join(USER_CONTEXT_YAMLS_DIR, f"{context_name}.yml")
+ path = os.path.join(SerenaPaths().user_contexts_dir, f"{context_name}.yml")
if not os.path.exists(path):
click.echo(f"Custom context '{context_name}' not found.")
return
@@ -401,10 +465,9 @@ class SerenaConfigCommands(AutoRegisteringGroup):
"edit", help="Edit serena_config.yml in your default editor. Will create a config file from the template if no config is found."
)
def edit() -> None:
- config_path = os.path.join(SERENA_MANAGED_DIR_IN_HOME, "serena_config.yml")
- if not os.path.exists(config_path):
- SerenaConfig.generate_config_file(config_path)
- _open_in_editor(config_path)
+ serena_config = SerenaConfig.from_config_file()
+ assert serena_config.config_file_path is not None
+ _open_in_editor(serena_config.config_file_path)
class ProjectCommands(AutoRegisteringGroup):
@@ -811,8 +874,9 @@ class PromptCommands(AutoRegisteringGroup):
@staticmethod
def _get_user_prompt_yaml_path(prompt_yaml_name: str) -> str:
- os.makedirs(PROMPT_TEMPLATES_DIR_IN_USER_HOME, exist_ok=True)
- return os.path.join(PROMPT_TEMPLATES_DIR_IN_USER_HOME, prompt_yaml_name)
+ templates_dir = SerenaPaths().user_prompt_templates_dir
+ os.makedirs(templates_dir, exist_ok=True)
+ return os.path.join(templates_dir, prompt_yaml_name)
@staticmethod
@click.command("list", help="Lists yamls that are used for defining prompts.")
@@ -863,9 +927,10 @@ class PromptCommands(AutoRegisteringGroup):
@staticmethod
@click.command("list-overrides", help="List existing prompt override files")
def list_overrides() -> None:
- os.makedirs(PROMPT_TEMPLATES_DIR_IN_USER_HOME, exist_ok=True)
+ user_templates_dir = SerenaPaths().user_prompt_templates_dir
+ os.makedirs(user_templates_dir, exist_ok=True)
serena_prompt_yaml_names = [os.path.basename(f) for f in glob.glob(PROMPT_TEMPLATES_DIR_INTERNAL + "/*.yml")]
- override_files = glob.glob(os.path.join(PROMPT_TEMPLATES_DIR_IN_USER_HOME, "*.yml"))
+ override_files = glob.glob(os.path.join(user_templates_dir, "*.yml"))
for file_path in override_files:
if os.path.basename(file_path) in serena_prompt_yaml_names:
click.echo(file_path)
diff --git a/src/serena/code_editor.py b/src/serena/code_editor.py
index 6e9f65b1..4f5fe6bb 100644
--- a/src/serena/code_editor.py
+++ b/src/serena/code_editor.py
@@ -9,7 +9,6 @@ from typing import TYPE_CHECKING, Generic, Optional, TypeVar, cast
from serena.symbol import JetBrainsSymbol, LanguageServerSymbol, LanguageServerSymbolRetriever, PositionInFile, Symbol
from solidlsp import SolidLanguageServer, ls_types
from solidlsp.ls import LSPFileBuffer
-from solidlsp.ls_types import extract_text_edits
from solidlsp.ls_utils import PathUtils, TextUtils
from .constants import DEFAULT_SOURCE_FILE_ENCODING
@@ -38,12 +37,23 @@ class CodeEditor(Generic[TSymbol], ABC):
self.encoding = encoding
class EditedFile(ABC):
+ def __init__(self, relative_path: str) -> None:
+ self.relative_path = relative_path
+
@abstractmethod
def get_contents(self) -> str:
"""
:return: the contents of the file.
"""
+ @abstractmethod
+ def set_contents(self, contents: str) -> None:
+ """
+ Fully resets the contents of the file.
+
+ :param contents: the new contents
+ """
+
@abstractmethod
def delete_text_between_positions(self, start_pos: PositionInFile, end_pos: PositionInFile) -> None:
pass
@@ -60,16 +70,19 @@ class CodeEditor(Generic[TSymbol], ABC):
raise NotImplementedError("This method must be overridden for each subclass")
@contextmanager
- def _edited_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]:
+ def edited_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]:
"""
Context manager for editing a file.
"""
with self._open_file_context(relative_path) as edited_file:
yield edited_file
# save the file
- abs_path = os.path.join(self.project_root, relative_path)
- with open(abs_path, "w", encoding=self.encoding) as f:
- f.write(edited_file.get_contents())
+ self._save_edited_file(edited_file)
+
+ def _save_edited_file(self, edited_file: "CodeEditor.EditedFile") -> None:
+ abs_path = os.path.join(self.project_root, edited_file.relative_path)
+ with open(abs_path, "w", encoding=self.encoding) as f:
+ f.write(edited_file.get_contents())
@abstractmethod
def _find_unique_symbol(self, name_path: str, relative_file_path: str) -> TSymbol:
@@ -94,7 +107,7 @@ class CodeEditor(Generic[TSymbol], ABC):
start_pos = symbol.get_body_start_position_or_raise()
end_pos = symbol.get_body_end_position_or_raise()
- with self._edited_file_context(relative_file_path) as edited_file:
+ with self.edited_file_context(relative_file_path) as edited_file:
# make sure the replacement adds no additional newlines (before or after) - all newlines
# and whitespace before/after should remain the same, so we strip it entirely
body = body.strip()
@@ -149,7 +162,7 @@ class CodeEditor(Generic[TSymbol], ABC):
# `line += 1`, is replaced
body = body.rstrip("\r\n") + "\n"
- with self._edited_file_context(relative_file_path) as edited_file:
+ with self.edited_file_context(relative_file_path) as edited_file:
edited_file.insert_text_at_position(PositionInFile(line, col), body)
def insert_before_symbol(self, name_path: str, relative_file_path: str, body: str) -> None:
@@ -177,7 +190,7 @@ class CodeEditor(Generic[TSymbol], ABC):
body += "\n" * num_trailing_newlines
# apply edit
- with self._edited_file_context(relative_file_path) as edited_file:
+ with self.edited_file_context(relative_file_path) as edited_file:
edited_file.insert_text_at_position(PositionInFile(line=line, col=col), body)
def insert_at_line(self, relative_path: str, line: int, content: str) -> None:
@@ -188,7 +201,7 @@ class CodeEditor(Generic[TSymbol], ABC):
:param line: the 0-based index of the line to insert content at
:param content: the content to insert
"""
- with self._edited_file_context(relative_path) as edited_file:
+ with self.edited_file_context(relative_path) as edited_file:
edited_file.insert_text_at_position(PositionInFile(line, 0), content)
def delete_lines(self, relative_path: str, start_line: int, end_line: int) -> None:
@@ -202,7 +215,7 @@ class CodeEditor(Generic[TSymbol], ABC):
start_col = 0
end_line_for_delete = end_line + 1
end_col = 0
- with self._edited_file_context(relative_path) as edited_file:
+ with self.edited_file_context(relative_path) as edited_file:
start_pos = PositionInFile(line=start_line, col=start_col)
end_pos = PositionInFile(line=end_line_for_delete, col=end_col)
edited_file.delete_text_between_positions(start_pos, end_pos)
@@ -214,7 +227,7 @@ class CodeEditor(Generic[TSymbol], ABC):
symbol = self._find_unique_symbol(name_path, relative_file_path)
start_pos = symbol.get_body_start_position_or_raise()
end_pos = symbol.get_body_end_position_or_raise()
- with self._edited_file_context(relative_file_path) as edited_file:
+ with self.edited_file_context(relative_file_path) as edited_file:
edited_file.delete_text_between_positions(start_pos, end_pos)
@abstractmethod
@@ -239,21 +252,24 @@ class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]):
class EditedFile(CodeEditor.EditedFile):
def __init__(self, lang_server: SolidLanguageServer, relative_path: str, file_buffer: LSPFileBuffer):
+ super().__init__(relative_path)
self._lang_server = lang_server
- self._relative_path = relative_path
self._file_buffer = file_buffer
def get_contents(self) -> str:
return self._file_buffer.contents
+ def set_contents(self, contents: str) -> None:
+ self._file_buffer.contents = contents
+
def delete_text_between_positions(self, start_pos: PositionInFile, end_pos: PositionInFile) -> None:
- self._lang_server.delete_text_between_positions(self._relative_path, start_pos.to_lsp_position(), end_pos.to_lsp_position())
+ self._lang_server.delete_text_between_positions(self.relative_path, start_pos.to_lsp_position(), end_pos.to_lsp_position())
def insert_text_at_position(self, pos: PositionInFile, text: str) -> None:
- self._lang_server.insert_text_at_position(self._relative_path, pos.line, pos.col, text)
+ self._lang_server.insert_text_at_position(self.relative_path, pos.line, pos.col, text)
def apply_text_edits(self, text_edits: list[ls_types.TextEdit]) -> None:
- return self._lang_server.apply_text_edits_to_file(self._relative_path, text_edits)
+ return self._lang_server.apply_text_edits_to_file(self.relative_path, text_edits)
@contextmanager
def _open_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]:
@@ -267,35 +283,70 @@ class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]):
return lang_server.language_server.retrieve_full_file_content(relative_path)
def _find_unique_symbol(self, name_path: str, relative_file_path: str) -> LanguageServerSymbol:
- symbol_candidates = self._symbol_retriever.find_by_name(name_path, within_relative_path=relative_file_path)
- if len(symbol_candidates) == 0:
- raise ValueError(f"No symbol with name {name_path} found in file {relative_file_path}")
- if len(symbol_candidates) > 1:
- raise ValueError(
- f"Found multiple {len(symbol_candidates)} symbols with name {name_path} in file {relative_file_path}. "
- "Their locations are: \n " + json.dumps([s.location.to_dict() for s in symbol_candidates], indent=2)
- )
- return symbol_candidates[0]
+ return self._symbol_retriever.find_unique(name_path, within_relative_path=relative_file_path)
- def _apply_workspace_edit(self, workspace_edit: ls_types.WorkspaceEdit) -> list[str]:
- """
- Apply a WorkspaceEdit by making the changes to files.
+ def _relative_path_from_uri(self, uri: str) -> str:
+ return os.path.relpath(PathUtils.uri_to_path(uri), self.project_root)
- :param workspace_edit: The WorkspaceEdit containing the changes to apply
- :return: List of relative file paths that were modified
- """
- uri_to_edits = extract_text_edits(workspace_edit)
- modified_relative_paths = []
+ class EditOperation(ABC):
+ @abstractmethod
+ def apply(self) -> None:
+ pass
- # Handle the 'changes' format (URI -> list of TextEdits)
- for uri, edits in uri_to_edits.items():
- file_path = PathUtils.uri_to_path(uri)
- relative_path = os.path.relpath(file_path, self._symbol_retriever.get_root_path())
- modified_relative_paths.append(relative_path)
- with self._edited_file_context(relative_path) as edited_file:
+ class EditOperationFileTextEdits(EditOperation):
+ def __init__(self, code_editor: "LanguageServerCodeEditor", file_uri: str, text_edits: list[ls_types.TextEdit]):
+ self._code_editor = code_editor
+ self._relative_path = code_editor._relative_path_from_uri(file_uri)
+ self._text_edits = text_edits
+
+ def apply(self) -> None:
+ with self._code_editor.edited_file_context(self._relative_path) as edited_file:
edited_file = cast(LanguageServerCodeEditor.EditedFile, edited_file)
- edited_file.apply_text_edits(edits)
- return modified_relative_paths
+ edited_file.apply_text_edits(self._text_edits)
+
+ class EditOperationRenameFile(EditOperation):
+ def __init__(self, code_editor: "LanguageServerCodeEditor", old_uri: str, new_uri: str):
+ self._code_editor = code_editor
+ self._old_relative_path = code_editor._relative_path_from_uri(old_uri)
+ self._new_relative_path = code_editor._relative_path_from_uri(new_uri)
+
+ def apply(self) -> None:
+ old_abs_path = os.path.join(self._code_editor.project_root, self._old_relative_path)
+ new_abs_path = os.path.join(self._code_editor.project_root, self._new_relative_path)
+ os.rename(old_abs_path, new_abs_path)
+
+ def _workspace_edit_to_edit_operations(self, workspace_edit: ls_types.WorkspaceEdit) -> list["LanguageServerCodeEditor.EditOperation"]:
+ operations: list[LanguageServerCodeEditor.EditOperation] = []
+
+ if "changes" in workspace_edit:
+ for uri, edits in workspace_edit["changes"].items():
+ operations.append(self.EditOperationFileTextEdits(self, uri, edits))
+
+ if "documentChanges" in workspace_edit:
+ for change in workspace_edit["documentChanges"]:
+ if "textDocument" in change and "edits" in change:
+ operations.append(self.EditOperationFileTextEdits(self, change["textDocument"]["uri"], change["edits"]))
+ elif "kind" in change:
+ if change["kind"] == "rename":
+ operations.append(self.EditOperationRenameFile(self, change["oldUri"], change["newUri"]))
+ else:
+ raise ValueError(f"Unhandled document change kind: {change}; Please report to Serena developers.")
+ else:
+ raise ValueError(f"Unhandled document change format: {change}; Please report to Serena developers.")
+
+ return operations
+
+ def _apply_workspace_edit(self, workspace_edit: ls_types.WorkspaceEdit) -> int:
+ """
+ Applies a WorkspaceEdit
+
+ :param workspace_edit: the edit to apply
+ :return: number of edit operations applied
+ """
+ operations = self._workspace_edit_to_edit_operations(workspace_edit)
+ for operation in operations:
+ operation.apply()
+ return len(operations)
def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> str:
symbol = self._find_unique_symbol(name_path, relative_file_path)
@@ -315,8 +366,14 @@ class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]):
f"Language server for {lang_server.language_id} returned no rename edits for symbol '{name_path}'. "
f"The symbol might not support renaming."
)
- modified_files = self._apply_workspace_edit(rename_result)
- msg = f"Successfully renamed '{name_path}' to '{new_name}' in {len(modified_files)} file(s)"
+ num_changes = self._apply_workspace_edit(rename_result)
+
+ if num_changes == 0:
+ raise ValueError(
+ f"Renaming symbol '{name_path}' to '{new_name}' resulted in no changes being applied; renaming may not be supported."
+ )
+
+ msg = f"Successfully renamed '{name_path}' to '{new_name}' ({num_changes} changes applied)"
return msg
@@ -327,6 +384,7 @@ class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]):
class EditedFile(CodeEditor.EditedFile):
def __init__(self, relative_path: str, project: Project):
+ super().__init__(relative_path)
path = os.path.join(project.project_root, relative_path)
log.info("Editing file: %s", path)
with open(path, encoding=project.project_config.encoding) as f:
@@ -335,6 +393,9 @@ class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]):
def get_contents(self) -> str:
return self._content
+ def set_contents(self, contents: str) -> None:
+ self._content = contents
+
def delete_text_between_positions(self, start_pos: PositionInFile, end_pos: PositionInFile) -> None:
self._content, _ = TextUtils.delete_text_between_positions(
self._content, start_pos.line, start_pos.col, end_pos.line, end_pos.col
@@ -347,6 +408,11 @@ class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]):
def _open_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]:
yield self.EditedFile(relative_path, self._project)
+ def _save_edited_file(self, edited_file: "CodeEditor.EditedFile") -> None:
+ super()._save_edited_file(edited_file)
+ with JetBrainsPluginClient.from_project(self._project) as client:
+ client.refresh_file(edited_file.relative_path)
+
def _find_unique_symbol(self, name_path: str, relative_file_path: str) -> JetBrainsSymbol:
with JetBrainsPluginClient.from_project(self._project) as client:
result = client.find_symbol(name_path, relative_path=relative_file_path, include_body=False, depth=0, include_location=True)
diff --git a/src/serena/config/context_mode.py b/src/serena/config/context_mode.py
index 7a6fc6e9..f1f34fd9 100644
--- a/src/serena/config/context_mode.py
+++ b/src/serena/config/context_mode.py
@@ -4,7 +4,6 @@ Context and Mode configuration loader
import os
from dataclasses import dataclass, field
-from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING, Self
@@ -12,7 +11,7 @@ import yaml
from sensai.util import logging
from sensai.util.string import ToStringMixin
-from serena.config.serena_config import ToolInclusionDefinition
+from serena.config.serena_config import SerenaPaths, ToolInclusionDefinition
from serena.constants import (
DEFAULT_CONTEXT,
DEFAULT_MODES,
@@ -20,8 +19,6 @@ from serena.constants import (
SERENA_FILE_ENCODING,
SERENAS_OWN_CONTEXT_YAMLS_DIR,
SERENAS_OWN_MODE_YAMLS_DIR,
- USER_CONTEXT_YAMLS_DIR,
- USER_MODE_YAMLS_DIR,
)
if TYPE_CHECKING:
@@ -66,14 +63,14 @@ class SerenaAgentMode(ToolInclusionDefinition, ToStringMixin):
def get_path(cls, name: str) -> str:
"""Get the path to the YAML file for a mode."""
fname = f"{name}.yml"
- custom_mode_path = os.path.join(USER_MODE_YAMLS_DIR, fname)
+ custom_mode_path = os.path.join(SerenaPaths().user_modes_dir, fname)
if os.path.exists(custom_mode_path):
return custom_mode_path
own_yaml_path = os.path.join(SERENAS_OWN_MODE_YAMLS_DIR, fname)
if not os.path.exists(own_yaml_path):
raise FileNotFoundError(
- f"Mode {name} not found in {USER_MODE_YAMLS_DIR} or in {SERENAS_OWN_MODE_YAMLS_DIR}."
+ f"Mode {name} not found in {SerenaPaths().user_modes_dir} or in {SERENAS_OWN_MODE_YAMLS_DIR}."
f"Available modes:\n{cls.list_registered_mode_names()}"
)
return own_yaml_path
@@ -103,7 +100,7 @@ class SerenaAgentMode(ToolInclusionDefinition, ToStringMixin):
@classmethod
def list_custom_mode_names(cls) -> list[str]:
"""Names of all custom modes defined by the user."""
- return [f.stem for f in Path(USER_MODE_YAMLS_DIR).glob("*.yml")]
+ return [f.stem for f in Path(SerenaPaths().user_modes_dir).glob("*.yml")]
@classmethod
def load_default_modes(cls) -> list[Self]:
@@ -167,14 +164,14 @@ class SerenaAgentContext(ToolInclusionDefinition, ToStringMixin):
def get_path(cls, name: str) -> str:
"""Get the path to the YAML file for a context."""
fname = f"{name}.yml"
- custom_context_path = os.path.join(USER_CONTEXT_YAMLS_DIR, fname)
+ custom_context_path = os.path.join(SerenaPaths().user_contexts_dir, fname)
if os.path.exists(custom_context_path):
return custom_context_path
own_yaml_path = os.path.join(SERENAS_OWN_CONTEXT_YAMLS_DIR, fname)
if not os.path.exists(own_yaml_path):
raise FileNotFoundError(
- f"Context {name} not found in {USER_CONTEXT_YAMLS_DIR} or in {SERENAS_OWN_CONTEXT_YAMLS_DIR}."
+ f"Context {name} not found in {SerenaPaths().user_contexts_dir} or in {SERENAS_OWN_CONTEXT_YAMLS_DIR}."
f"Available contexts:\n{cls.list_registered_context_names()}"
)
return own_yaml_path
@@ -182,6 +179,16 @@ class SerenaAgentContext(ToolInclusionDefinition, ToStringMixin):
@classmethod
def from_name(cls, name: str) -> Self:
"""Load a registered Serena context."""
+ legacy_name_mapping = {
+ "ide-assistant": "claude-code",
+ }
+ if name in legacy_name_mapping:
+ log.warning(
+ f"Context name '{name}' is deprecated and has been renamed to '{legacy_name_mapping[name]}'. "
+ f"Please update your configuration; refer to the configuration guide for more details: "
+ "https://oraios.github.io/serena/02-usage/050_configuration.html#contexts"
+ )
+ name = legacy_name_mapping[name]
context_path = cls.get_path(name)
return cls.from_yaml(context_path)
@@ -202,7 +209,7 @@ class SerenaAgentContext(ToolInclusionDefinition, ToStringMixin):
@classmethod
def list_custom_context_names(cls) -> list[str]:
"""Names of all custom contexts defined by the user."""
- return [f.stem for f in Path(USER_CONTEXT_YAMLS_DIR).glob("*.yml")]
+ return [f.stem for f in Path(SerenaPaths().user_contexts_dir).glob("*.yml")]
@classmethod
def load_default(cls) -> Self:
@@ -214,35 +221,3 @@ class SerenaAgentContext(ToolInclusionDefinition, ToStringMixin):
print(f"{self.name}:\n {self.description}")
if self.excluded_tools:
print(" excluded tools:\n " + ", ".join(sorted(self.excluded_tools)))
-
-
-class RegisteredContext(Enum):
- """A registered context."""
-
- IDE_ASSISTANT = "ide-assistant"
- """For Serena running within an assistant that already has basic tools, like Claude Code, Cline, Cursor, etc."""
- DESKTOP_APP = "desktop-app"
- """For Serena running within Claude Desktop or a similar app which does not have built-in tools for code editing."""
- AGENT = "agent"
- """For Serena running as a standalone agent, e.g. through agno."""
-
- def load(self) -> SerenaAgentContext:
- """Load the context."""
- return SerenaAgentContext.from_name(self.value)
-
-
-class RegisteredMode(Enum):
- """A registered mode."""
-
- INTERACTIVE = "interactive"
- """Interactive mode, for multi-turn interactions."""
- EDITING = "editing"
- """Editing tools are activated."""
- PLANNING = "planning"
- """Editing tools are deactivated."""
- ONE_SHOT = "one-shot"
- """Non-interactive mode, where the goal is to finish a task autonomously."""
-
- def load(self) -> SerenaAgentMode:
- """Load the mode."""
- return SerenaAgentMode.from_name(self.value)
diff --git a/src/serena/config/serena_config.py b/src/serena/config/serena_config.py
index 6cc74f84..7933c214 100644
--- a/src/serena/config/serena_config.py
+++ b/src/serena/config/serena_config.py
@@ -9,6 +9,7 @@ from collections.abc import Sequence
from copy import deepcopy
from dataclasses import dataclass, field
from datetime import datetime
+from enum import Enum
from functools import cached_property
from pathlib import Path
from typing import TYPE_CHECKING, Any, Optional, Self, TypeVar
@@ -25,10 +26,9 @@ from serena.constants import (
REPO_ROOT,
SERENA_CONFIG_TEMPLATE_FILE,
SERENA_FILE_ENCODING,
- SERENA_MANAGED_DIR_IN_HOME,
SERENA_MANAGED_DIR_NAME,
)
-from serena.util.general import load_yaml, save_yaml
+from serena.util.general import get_dataclass_default, load_yaml, save_yaml
from serena.util.inspection import determine_programming_language_composition
from solidlsp.ls_config import Language
@@ -53,9 +53,32 @@ class SerenaPaths:
"""
def __init__(self) -> None:
- self.user_config_dir: str = SERENA_MANAGED_DIR_IN_HOME
+ home_dir = os.getenv("SERENA_HOME")
+ if home_dir is None or home_dir.strip() == "":
+ home_dir = str(Path.home() / SERENA_MANAGED_DIR_NAME)
+ else:
+ home_dir = home_dir.strip()
+ self.serena_user_home_dir: str = home_dir
"""
- the path to the user's Serena configuration directory, which is typically ~/.serena
+ the path to the Serena home directory, where the user's configuration/data is stored.
+ This is ~/.serena by default, but it can be overridden via the SERENA_HOME environment variable.
+ """
+ self.user_prompt_templates_dir: str = os.path.join(self.serena_user_home_dir, "prompt_templates")
+ """
+ directory containing prompt templates defined by the user.
+ Prompts defined by the user take precedence over Serena's built-in prompt templates.
+ """
+ self.user_contexts_dir: str = os.path.join(self.serena_user_home_dir, "contexts")
+ """
+ directory containing contexts defined by the user.
+ If a name of a context matches a name of a context in SERENAS_OWN_CONTEXT_YAMLS_DIR,
+ the user context will override the default context definition.
+ """
+ self.user_modes_dir: str = os.path.join(self.serena_user_home_dir, "modes")
+ """
+ directory containing modes defined by the user.
+ If a name of a mode matches a name of a mode in SERENAS_OWN_MODES_YAML_DIR,
+ the user mode will override the default mode definition.
"""
def get_next_log_file_path(self, prefix: str) -> str:
@@ -63,7 +86,7 @@ class SerenaPaths:
:param prefix: the filename prefix indicating the type of the log file
:return: the full path to the log file to use
"""
- log_dir = os.path.join(self.user_config_dir, "logs", datetime.now().strftime("%Y-%m-%d"))
+ log_dir = os.path.join(self.serena_user_home_dir, "logs", datetime.now().strftime("%Y-%m-%d"))
os.makedirs(log_dir, exist_ok=True)
return os.path.join(log_dir, prefix + "_" + datetime_tag() + ".txt")
@@ -108,17 +131,24 @@ def get_serena_managed_in_project_dir(project_root: str | Path) -> str:
return os.path.join(project_root, SERENA_MANAGED_DIR_NAME)
-def is_running_in_docker() -> bool:
- """Check if we're running inside a Docker container."""
- # Check for Docker-specific files
- if os.path.exists("/.dockerenv"):
- return True
- # Check cgroup for docker references
- try:
- with open("/proc/self/cgroup") as f:
- return "docker" in f.read()
- except FileNotFoundError:
- return False
+class LanguageBackend(Enum):
+ LSP = "LSP"
+ """
+ Use the language server protocol (LSP), spawning freely available language servers
+ via the SolidLSP library that is part of Serena
+ """
+ JETBRAINS = "JetBrains"
+ """
+ Use the Serena plugin in your JetBrains IDE.
+ (requires the plugin to be installed and the project being worked on to be open in your IDE)
+ """
+
+ @staticmethod
+ def from_str(backend_str: str) -> "LanguageBackend":
+ for backend in LanguageBackend:
+ if backend.value.lower() == backend_str.lower():
+ return backend
+ raise ValueError(f"Unknown language backend '{backend_str}': valid values are {[b.value for b in LanguageBackend]}")
@dataclass(kw_only=True)
@@ -160,10 +190,13 @@ class ProjectConfig(ToolInclusionDefinition, ToStringMixin):
if not project_root.exists():
raise FileNotFoundError(f"Project root not found: {project_root}")
with LogTime("Project configuration auto-generation", logger=log):
+ log.info("Project root: %s", project_root)
project_name = project_name or project_root.name
if languages is None:
# determine languages automatically
+ log.info("Determining programming languages used in the project")
language_composition = determine_programming_language_composition(str(project_root))
+ log.info("Language composition: %s", language_composition)
if len(language_composition) == 0:
language_values = ", ".join([lang.value for lang in Language])
raise ValueError(
@@ -176,15 +209,18 @@ class ProjectConfig(ToolInclusionDefinition, ToStringMixin):
f"Read the documentation for more information."
)
# sort languages by number of files found
- languages_and_percentages = sorted(language_composition.items(), key=lambda item: item[1], reverse=True)
+ languages_and_percentages = sorted(
+ language_composition.items(), key=lambda item: (item[1], item[0].get_priority()), reverse=True
+ )
# find the language with the highest percentage and enable it
top_language_pair = languages_and_percentages[0]
other_language_pairs = languages_and_percentages[1:]
- languages_to_use = [top_language_pair[0]]
+ languages_to_use: list[str] = [top_language_pair[0].value]
# if in interactive mode, ask the user which other languages to enable
if len(other_language_pairs) > 0 and interactive:
print(
- "Detected and enabled main language '%s' (%.2f%% of source files)." % (top_language_pair[0], top_language_pair[1])
+ "Detected and enabled main language '%s' (%.2f%% of source files)."
+ % (top_language_pair[0].value, top_language_pair[1])
)
print(f"Additionally detected {len(other_language_pairs)} other language(s).\n")
print("Note: Enable only languages you need symbolic retrieval/editing capabilities for.")
@@ -192,17 +228,20 @@ class ProjectConfig(ToolInclusionDefinition, ToStringMixin):
print(" system-level installations/configuration (see Serena documentation).")
print("\nWhich additional languages do you want to enable?")
for lang, perc in other_language_pairs:
- enable = ask_yes_no("Enable %s (%.2f%% of source files)?" % (lang, perc), default=False)
+ enable = ask_yes_no("Enable %s (%.2f%% of source files)?" % (lang.value, perc), default=False)
if enable:
- languages_to_use.append(lang)
+ languages_to_use.append(lang.value)
print()
+ log.info("Using languages: %s", languages_to_use)
else:
languages_to_use = [lang.value for lang in languages]
config_with_comments = cls.load_commented_map(PROJECT_TEMPLATE_FILE)
config_with_comments["project_name"] = project_name
config_with_comments["languages"] = languages_to_use
if save_to_disk:
- save_yaml(cls.path_to_project_yml(project_root), config_with_comments, preserve_comments=True)
+ project_yml_path = cls.path_to_project_yml(project_root)
+ log.info("Saving project configuration to %s", project_yml_path)
+ save_yaml(project_yml_path, config_with_comments, preserve_comments=True)
return cls._from_dict(config_with_comments)
@classmethod
@@ -366,6 +405,7 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
trace_lsp_communication: bool = False
web_dashboard: bool = True
web_dashboard_open_on_launch: bool = True
+ web_dashboard_listen_address: str = "127.0.0.1"
tool_timeout: float = DEFAULT_TOOL_TIMEOUT
loaded_commented_yaml: CommentedMap | None = None
config_file_path: str | None = None
@@ -373,9 +413,10 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
the path to the configuration file to which updates of the configuration shall be saved;
if None, the configuration is not saved to disk
"""
- jetbrains: bool = False
+
+ language_backend: LanguageBackend = LanguageBackend.LSP
"""
- whether to apply JetBrains mode
+ the language backend to use for code understanding features
"""
token_count_estimator: str = RegisteredTokenCountEstimator.CHAR_COUNT.name
@@ -395,13 +436,12 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
"""Advanced configuration option allowing to configure language server implementation specific options, see SolidLSPSettings for more info."""
CONFIG_FILE = "serena_config.yml"
- CONFIG_FILE_DOCKER = "serena_config.docker.yml" # Docker-specific config file; auto-generated if missing, mounted via docker-compose for user customization
def _tostring_includes(self) -> list[str]:
return ["config_file_path"]
@classmethod
- def generate_config_file(cls, config_file_path: str) -> None:
+ def _generate_config_file(cls, config_file_path: str) -> None:
"""
Generates a Serena configuration file at the specified path from the template file.
@@ -416,20 +456,17 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
"""
:return: the location where the Serena configuration file is stored/should be stored
"""
- if is_running_in_docker():
- return os.path.join(REPO_ROOT, cls.CONFIG_FILE_DOCKER)
- else:
- config_path = os.path.join(SERENA_MANAGED_DIR_IN_HOME, cls.CONFIG_FILE)
+ config_path = os.path.join(SerenaPaths().serena_user_home_dir, cls.CONFIG_FILE)
- # if the config file does not exist, check if we can migrate it from the old location
- if not os.path.exists(config_path):
- old_config_path = os.path.join(REPO_ROOT, cls.CONFIG_FILE)
- if os.path.exists(old_config_path):
- log.info(f"Moving Serena configuration file from {old_config_path} to {config_path}")
- os.makedirs(os.path.dirname(config_path), exist_ok=True)
- shutil.move(old_config_path, config_path)
+ # if the config file does not exist, check if we can migrate it from the old location
+ if not os.path.exists(config_path):
+ old_config_path = os.path.join(REPO_ROOT, cls.CONFIG_FILE)
+ if os.path.exists(old_config_path):
+ log.info(f"Moving Serena configuration file from {old_config_path} to {config_path}")
+ os.makedirs(os.path.dirname(config_path), exist_ok=True)
+ shutil.move(old_config_path, config_path)
- return config_path
+ return config_path
@classmethod
def from_config_file(cls, generate_if_missing: bool = True) -> "SerenaConfig":
@@ -443,7 +480,7 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
if not generate_if_missing:
raise FileNotFoundError(f"Serena configuration file not found: {config_file_path}")
log.info(f"Serena configuration file not found at {config_file_path}, autogenerating...")
- cls.generate_config_file(config_file_path)
+ cls._generate_config_file(config_file_path)
# load the configuration
log.info(f"Loading Serena configuration from {config_file_path}")
@@ -461,7 +498,7 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
# load list of known projects
instance.projects = []
- num_project_migrations = 0
+ num_migrations = 0
for path in loaded_commented_yaml["projects"]:
path = Path(path).resolve()
if not path.exists() or (path.is_dir() and not (path / ProjectConfig.rel_path_to_project_yml()).exists()):
@@ -471,7 +508,7 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
path = cls._migrate_out_of_project_config_file(path)
if path is None:
continue
- num_project_migrations += 1
+ num_migrations += 1
project_config = ProjectConfig.load(path)
project = RegisteredProject(
project_root=str(path),
@@ -479,30 +516,42 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
)
instance.projects.append(project)
- # set other configuration parameters
- if is_running_in_docker():
- instance.gui_log_window_enabled = False # not supported in Docker
+ def get_value_or_default(key: str, field_name: str | None = None) -> Any:
+ if field_name is None:
+ field_name = key
+ return loaded_commented_yaml.get(key, get_dataclass_default(SerenaConfig, field_name))
+
+ # determine language backend
+ language_backend = get_dataclass_default(SerenaConfig, "language_backend")
+ if "language_backend" in loaded_commented_yaml:
+ backend_str = loaded_commented_yaml["language_backend"]
+ language_backend = LanguageBackend.from_str(backend_str)
else:
- instance.gui_log_window_enabled = loaded_commented_yaml.get("gui_log_window", False)
+ # backward compatibility (migrate Boolean field "jetbrains")
+ if "jetbrains" in loaded_commented_yaml:
+ num_migrations += 1
+ if loaded_commented_yaml["jetbrains"]:
+ language_backend = LanguageBackend.JETBRAINS
+ del loaded_commented_yaml["jetbrains"]
+ instance.language_backend = language_backend
+
+ # set other configuration parameters (primitive types)
+ instance.gui_log_window_enabled = get_value_or_default("gui_log_window", "gui_log_window_enabled")
+ instance.web_dashboard_listen_address = get_value_or_default("web_dashboard_listen_address")
instance.log_level = loaded_commented_yaml.get("log_level", loaded_commented_yaml.get("gui_log_level", logging.INFO))
- instance.web_dashboard = loaded_commented_yaml.get("web_dashboard", True)
- instance.web_dashboard_open_on_launch = loaded_commented_yaml.get("web_dashboard_open_on_launch", True)
- instance.tool_timeout = loaded_commented_yaml.get("tool_timeout", DEFAULT_TOOL_TIMEOUT)
- instance.trace_lsp_communication = loaded_commented_yaml.get("trace_lsp_communication", False)
- instance.excluded_tools = loaded_commented_yaml.get("excluded_tools", [])
- instance.included_optional_tools = loaded_commented_yaml.get("included_optional_tools", [])
- instance.jetbrains = loaded_commented_yaml.get("jetbrains", False)
- instance.token_count_estimator = loaded_commented_yaml.get(
- "token_count_estimator", RegisteredTokenCountEstimator.TIKTOKEN_GPT4O.name
- )
- instance.default_max_tool_answer_chars = loaded_commented_yaml.get("default_max_tool_answer_chars", 150_000)
- instance.ls_specific_settings = loaded_commented_yaml.get("ls_specific_settings", {})
+ instance.web_dashboard = get_value_or_default("web_dashboard")
+ instance.web_dashboard_open_on_launch = get_value_or_default("web_dashboard_open_on_launch")
+ instance.tool_timeout = get_value_or_default("tool_timeout")
+ instance.trace_lsp_communication = get_value_or_default("trace_lsp_communication")
+ instance.excluded_tools = get_value_or_default("excluded_tools")
+ instance.included_optional_tools = get_value_or_default("included_optional_tools")
+ instance.token_count_estimator = get_value_or_default("token_count_estimator")
+ instance.default_max_tool_answer_chars = get_value_or_default("default_max_tool_answer_chars")
+ instance.ls_specific_settings = get_value_or_default("ls_specific_settings")
# re-save the configuration file if any migrations were performed
- if num_project_migrations > 0:
- log.info(
- f"Migrated {num_project_migrations} project configurations from legacy format to in-project configuration; re-saving configuration"
- )
+ if num_migrations > 0:
+ log.info("Legacy configuration was migrated; re-saving configuration file")
instance.save()
return instance
@@ -605,9 +654,15 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
"""
if self.config_file_path is None:
return
+
assert self.loaded_commented_yaml is not None, "Cannot save configuration without loaded YAML"
+
loaded_original_yaml = deepcopy(self.loaded_commented_yaml)
- # projects are unique absolute paths
- # we also canonicalize them before saving
+
+ # convert project objects into list of paths
loaded_original_yaml["projects"] = sorted({str(project.project_root) for project in self.projects})
+
+ # convert language backend to string
+ loaded_original_yaml["language_backend"] = self.language_backend.value
+
save_yaml(self.config_file_path, loaded_original_yaml, preserve_comments=True)
diff --git a/src/serena/constants.py b/src/serena/constants.py
index e0ed29cf..f5ccd660 100644
--- a/src/serena/constants.py
+++ b/src/serena/constants.py
@@ -4,22 +4,14 @@ _repo_root_path = Path(__file__).parent.parent.parent.resolve()
_serena_pkg_path = Path(__file__).parent.resolve()
SERENA_MANAGED_DIR_NAME = ".serena"
-_serena_in_home_managed_dir = Path.home() / ".serena"
-
-SERENA_MANAGED_DIR_IN_HOME = str(_serena_in_home_managed_dir)
# TODO: Path-related constants should be moved to SerenaPaths; don't add further constants here.
REPO_ROOT = str(_repo_root_path)
PROMPT_TEMPLATES_DIR_INTERNAL = str(_serena_pkg_path / "resources" / "config" / "prompt_templates")
-PROMPT_TEMPLATES_DIR_IN_USER_HOME = str(_serena_in_home_managed_dir / "prompt_templates")
SERENAS_OWN_CONTEXT_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "contexts")
"""The contexts that are shipped with the Serena package, i.e. the default contexts."""
-USER_CONTEXT_YAMLS_DIR = str(_serena_in_home_managed_dir / "contexts")
-"""Contexts defined by the user. If a name of a context matches a name of a context in SERENAS_OWN_CONTEXT_YAMLS_DIR, the user context will override the default one."""
SERENAS_OWN_MODE_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "modes")
"""The modes that are shipped with the Serena package, i.e. the default modes."""
-USER_MODE_YAMLS_DIR = str(_serena_in_home_managed_dir / "modes")
-"""Modes defined by the user. If a name of a mode matches a name of a mode in SERENAS_OWN_MODE_YAMLS_DIR, the user mode will override the default one."""
INTERNAL_MODE_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "internal_modes")
"""Internal modes, never overridden by user modes."""
SERENA_DASHBOARD_DIR = str(_serena_pkg_path / "resources" / "dashboard")
diff --git a/src/serena/dashboard.py b/src/serena/dashboard.py
index 6005bcad..55d8b87b 100644
--- a/src/serena/dashboard.py
+++ b/src/serena/dashboard.py
@@ -9,6 +9,7 @@ from pydantic import BaseModel
from sensai.util import logging
from serena.analytics import ToolUsageStats
+from serena.config.serena_config import LanguageBackend
from serena.constants import SERENA_DASHBOARD_DIR
from serena.task_executor import TaskExecutor
from serena.util.logging import MemoryLogHandler
@@ -443,7 +444,7 @@ class SerenaDashboardAPI:
available_modes=available_modes,
available_contexts=available_contexts,
available_memories=available_memories,
- jetbrains_mode=self._agent.serena_config.jetbrains,
+ jetbrains_mode=self._agent.serena_config.language_backend == LanguageBackend.JETBRAINS,
languages=languages,
encoding=encoding,
)
@@ -560,7 +561,7 @@ class SerenaDashboardAPI:
raise RuntimeError(f"No free ports found starting from {start_port}")
- def run(self, host: str = "0.0.0.0", port: int = 0x5EDA) -> int:
+ def run(self, host: str, port: int) -> int:
"""
Runs the dashboard on the given host and port and returns the port number.
"""
@@ -572,8 +573,9 @@ class SerenaDashboardAPI:
self._app.run(host=host, port=port, debug=False, use_reloader=False, threaded=True)
return port
- def run_in_thread(self) -> tuple[threading.Thread, int]:
+ def run_in_thread(self, host: str) -> tuple[threading.Thread, int]:
port = self._find_first_free_port(0x5EDA)
- thread = threading.Thread(target=lambda: self.run(port=port), daemon=True)
+ log.info("Starting dashboard (listen_address=%s, port=%d)", host, port)
+ thread = threading.Thread(target=lambda: self.run(host=host, port=port), daemon=True)
thread.start()
return thread, port
diff --git a/src/serena/ls_manager.py b/src/serena/ls_manager.py
index a3072286..8f3681bf 100644
--- a/src/serena/ls_manager.py
+++ b/src/serena/ls_manager.py
@@ -4,10 +4,10 @@ from collections.abc import Iterator
from sensai.util.logging import LogTime
-from serena.constants import SERENA_MANAGED_DIR_IN_HOME, SERENA_MANAGED_DIR_NAME
+from serena.config.serena_config import SerenaPaths
+from serena.constants import SERENA_MANAGED_DIR_NAME
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig
-from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
@@ -21,7 +21,6 @@ class LanguageServerFactory:
ignored_patterns: list[str],
ls_timeout: float | None = None,
ls_specific_settings: dict | None = None,
- log_level: int = logging.INFO,
trace_lsp_communication: bool = False,
):
self.project_root = project_root
@@ -29,7 +28,6 @@ class LanguageServerFactory:
self.ignored_patterns = ignored_patterns
self.ls_timeout = ls_timeout
self.ls_specific_settings = ls_specific_settings
- self.log_level = log_level
self.trace_lsp_communication = trace_lsp_communication
def create_language_server(self, language: Language) -> SolidLanguageServer:
@@ -39,16 +37,14 @@ class LanguageServerFactory:
trace_lsp_communication=self.trace_lsp_communication,
encoding=self.encoding,
)
- ls_logger = LanguageServerLogger(log_level=self.log_level)
log.info(f"Creating language server instance for {self.project_root}, language={language}.")
return SolidLanguageServer.create(
ls_config,
- ls_logger,
self.project_root,
timeout=self.ls_timeout,
solidlsp_settings=SolidLSPSettings(
- solidlsp_dir=SERENA_MANAGED_DIR_IN_HOME,
+ solidlsp_dir=SerenaPaths().serena_user_home_dir,
project_data_relative_path=SERENA_MANAGED_DIR_NAME,
ls_specific_settings=self.ls_specific_settings or {},
),
@@ -187,25 +183,26 @@ class LanguageServerManager:
self._stop_language_server(ls, save_cache=save_cache)
@staticmethod
- def _stop_language_server(ls: SolidLanguageServer, save_cache: bool = False) -> None:
+ def _stop_language_server(ls: SolidLanguageServer, save_cache: bool = False, timeout: float = 2.0) -> None:
if ls.is_running():
if save_cache:
ls.save_cache()
log.info(f"Stopping language server for language {ls.language} ...")
- ls.stop()
+ ls.stop(shutdown_timeout=timeout)
def iter_language_servers(self) -> Iterator[SolidLanguageServer]:
for ls in self._language_servers.values():
yield self._ensure_functional_ls(ls)
- def stop_all(self, save_cache: bool = False) -> None:
+ def stop_all(self, save_cache: bool = False, timeout: float = 2.0) -> None:
"""
Stops all managed language servers.
:param save_cache: whether to save the cache before stopping
+ :param timeout: timeout for shutdown of each language server
"""
for ls in self.iter_language_servers():
- self._stop_language_server(ls, save_cache=save_cache)
+ self._stop_language_server(ls, save_cache=save_cache, timeout=timeout)
def save_all_caches(self) -> None:
"""
diff --git a/src/serena/mcp.py b/src/serena/mcp.py
index 6030e9a0..2335ffd1 100644
--- a/src/serena/mcp.py
+++ b/src/serena/mcp.py
@@ -13,6 +13,7 @@ import docstring_parser
from mcp.server.fastmcp import server
from mcp.server.fastmcp.server import FastMCP, Settings
from mcp.server.fastmcp.tools.base import Tool as MCPTool
+from mcp.types import ToolAnnotations
from pydantic_settings import SettingsConfigDict
from sensai.util import logging
@@ -21,6 +22,7 @@ from serena.agent import (
SerenaConfig,
)
from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode
+from serena.config.serena_config import LanguageBackend
from serena.constants import DEFAULT_CONTEXT, DEFAULT_MODES, SERENA_LOG_FORMAT
from serena.tools import Tool
from serena.util.exception import show_fatal_exception_safe
@@ -219,6 +221,8 @@ class SerenaMCPFactory:
def execute_fn(**kwargs) -> str: # type: ignore
return tool.apply_ex(log_call=True, catch_exceptions=True, **kwargs)
+ annotations = ToolAnnotations(readOnlyHint=not tool.can_edit())
+
return MCPTool(
fn=execute_fn,
name=func_name,
@@ -227,7 +231,7 @@ class SerenaMCPFactory:
fn_metadata=func_arg_metadata,
is_async=is_async,
context_kwarg=None,
- annotations=None,
+ annotations=annotations,
title=None,
)
@@ -258,6 +262,7 @@ class SerenaMCPFactory:
host: str = "0.0.0.0",
port: int = 8000,
modes: Sequence[str] = DEFAULT_MODES,
+ language_backend: LanguageBackend | None = None,
enable_web_dashboard: bool | None = None,
enable_gui_log_window: bool | None = None,
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None = None,
@@ -270,6 +275,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 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.
If not specified, will take the value from the serena configuration.
@@ -293,6 +299,8 @@ class SerenaMCPFactory:
config.trace_lsp_communication = trace_lsp_communication
if tool_timeout is not None:
config.tool_timeout = tool_timeout
+ if language_backend is not None:
+ config.language_backend = language_backend
modes_instances = [SerenaAgentMode.load(mode) for mode in modes]
self.agent = self._create_serena_agent(config, modes_instances)
diff --git a/src/serena/project.py b/src/serena/project.py
index 3cd5eb08..968ad545 100644
--- a/src/serena/project.py
+++ b/src/serena/project.py
@@ -388,7 +388,6 @@ class Project(ToStringMixin):
ignored_patterns=self._ignored_patterns,
ls_timeout=ls_timeout,
ls_specific_settings=ls_specific_settings,
- log_level=log_level,
trace_lsp_communication=trace_lsp_communication,
)
self.language_server_manager = LanguageServerManager.from_languages(self.project_config.languages, factory)
@@ -439,7 +438,7 @@ class Project(ToStringMixin):
log.info("Removing and stopping the language server for language %s ...", language.value)
self.language_server_manager.remove_language_server(language)
- def shutdown(self) -> None:
+ def shutdown(self, timeout: float = 2.0) -> None:
if self.language_server_manager is not None:
- self.language_server_manager.stop_all(save_cache=True)
+ self.language_server_manager.stop_all(save_cache=True, timeout=timeout)
self.language_server_manager = None
diff --git a/src/serena/prompt_factory.py b/src/serena/prompt_factory.py
index 40f51e7a..31be6f49 100644
--- a/src/serena/prompt_factory.py
+++ b/src/serena/prompt_factory.py
@@ -1,6 +1,7 @@
import os
-from serena.constants import PROMPT_TEMPLATES_DIR_IN_USER_HOME, PROMPT_TEMPLATES_DIR_INTERNAL
+from serena.config.serena_config import SerenaPaths
+from serena.constants import PROMPT_TEMPLATES_DIR_INTERNAL
from serena.generated.generated_prompt_factory import PromptFactory
@@ -10,5 +11,6 @@ class SerenaPromptFactory(PromptFactory):
"""
def __init__(self) -> None:
- os.makedirs(PROMPT_TEMPLATES_DIR_IN_USER_HOME, exist_ok=True)
- super().__init__(prompts_dir=[PROMPT_TEMPLATES_DIR_IN_USER_HOME, PROMPT_TEMPLATES_DIR_INTERNAL])
+ user_templates_dir = SerenaPaths().user_prompt_templates_dir
+ os.makedirs(user_templates_dir, exist_ok=True)
+ super().__init__(prompts_dir=[user_templates_dir, PROMPT_TEMPLATES_DIR_INTERNAL])
diff --git a/src/serena/resources/config/contexts/agent.yml b/src/serena/resources/config/contexts/agent.yml
index e2984283..a3b324f0 100644
--- a/src/serena/resources/config/contexts/agent.yml
+++ b/src/serena/resources/config/contexts/agent.yml
@@ -1,7 +1,6 @@
-description: All tools except InitialInstructionsTool for agent context
+description: Agent context where the system prompt (initial instructions) are provided at startup
prompt: |
- You are running in agent context where the system prompt is provided externally. You should use symbolic
- tools when possible for code understanding and modification.
+ You are running in an agent context.
excluded_tools:
- initial_instructions
diff --git a/src/serena/resources/config/contexts/chatgpt.yml b/src/serena/resources/config/contexts/chatgpt.yml
index 4a035516..ae1c3db6 100644
--- a/src/serena/resources/config/contexts/chatgpt.yml
+++ b/src/serena/resources/config/contexts/chatgpt.yml
@@ -1,4 +1,4 @@
-description: A configuration specific for chatgpt, which has a limit of 30 tools and requires short descriptions.
+description: A configuration specific for ChatGPT, which has a limit of 30 tools and requires short descriptions.
prompt: |
You are running in desktop app context where the tools give you access to the code base as well as some
access to the file system, if configured. You interact with the user through a chat interface that is separated
diff --git a/src/serena/resources/config/contexts/claude-code.yml b/src/serena/resources/config/contexts/claude-code.yml
new file mode 100644
index 00000000..118ed8b4
--- /dev/null
+++ b/src/serena/resources/config/contexts/claude-code.yml
@@ -0,0 +1,26 @@
+description: Claude Code (CLI agent where file operations, basic edits, etc. are already covered; single project mode)
+prompt: |
+ You are running in a CLI coding agent context where file operations, basic (line-based) edits and reads
+ as well as shell commands are handled by your own, internal tools.
+
+ If Serena's tools can be used to achieve your task, you should prioritize them.
+ In particular, it is important that you avoid reading entire source code files unless it is strictly necessary!
+ Instead, for exploring and reading code in a token-efficient manner, use Serena's overview and symbolic search tools.
+ For non-code files or for reads where you don't know the symbol's name path, you can use the pattern search tool.
+
+excluded_tools:
+ - create_text_file
+ - read_file
+ - execute_shell_command
+ - prepare_for_new_conversation
+ - replace_content
+
+tool_description_overrides: {}
+
+# whether to assume that Serena shall only work on a single project in this context (provided that a project is given
+# when Serena is started).
+# If set to true and 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.
+# The `activate_project` tool is always disabled in this case, as project switching cannot be allowed.
+single_project: true
diff --git a/src/serena/resources/config/contexts/codex.yml b/src/serena/resources/config/contexts/codex.yml
index 2088ab32..a492e8f9 100644
--- a/src/serena/resources/config/contexts/codex.yml
+++ b/src/serena/resources/config/contexts/codex.yml
@@ -1,20 +1,13 @@
-description: Non-symbolic editing tools and general shell tool are excluded
+description: Codex Non-symbolic editing tools and general shell tool are excluded
prompt: |
- You are running in IDE assistant context where file operations, basic (line-based) edits and reads,
- and shell commands are handled by your own, internal tools.
- The initial instructions and the current config inform you on which tools are available to you,
- and how to use them.
- Don't attempt to use any excluded tools, instead rely on your own internal tools
- for achieving the basic file or shell operations.
+ You are running in the Codex IDE assistant mode, where file operations, basic (line-based) edits and reads
+ as well as shell commands are handled by your own, internal tools.
+ Don't attempt to use any excluded tools; instead, rely on your own internal tools for basic file or shell operations.
- If serena's tools can be used for achieving your task,
- you should prioritize them. In particular, it is important that you avoid reading entire source code files,
- unless it is strictly necessary! Instead, for exploring and reading code in a token-efficient manner,
- you should use serena's overview and symbolic search tools. The call of the read_file tool on an entire source code
- file should only happen in exceptional cases, usually you should first explore the file (by itself or as part of exploring
- the directory containing it) using the symbol_overview tool, and then make targeted reads using find_symbol and other symbolic tools.
- For non-code files or for reads where you don't know the symbol's name path you can use the patterns searching tool,
- using the read_file as a last resort.
+ If Serena's tools can be used to achieve your task, you should prioritize them.
+ In particular, it is important that you avoid reading entire source code files unless it is strictly necessary!
+ Instead, for exploring and reading code in a token-efficient manner, use Serena's overview and symbolic search tools.
+ For non-code files or for reads where you don't know the symbol's name path, you can use the pattern search tool.
excluded_tools:
- create_text_file
diff --git a/src/serena/resources/config/contexts/desktop-app.yml b/src/serena/resources/config/contexts/desktop-app.yml
index c3adcfb4..8d49cec5 100644
--- a/src/serena/resources/config/contexts/desktop-app.yml
+++ b/src/serena/resources/config/contexts/desktop-app.yml
@@ -1,15 +1,13 @@
-description: All tools included for desktop app context
+description: Desktop application context (chat application detached from code) where Serena's full toolset is provided
prompt: |
- You are running in desktop app context where the tools give you access to the code base as well as some
- access to the file system, if configured. You interact with the user through a chat interface that is separated
- from the code base. As a consequence, if you are in interactive mode, your communication with the user should
+ You are running in a desktop application context.
+ Serena's tools give you access to the code base as well as some access to the file system (if enabled).
+ You interact with the user through a chat interface that is separated from the code base.
+ As a consequence, if you are in interactive mode, your communication with the user should
involve high-level thinking and planning as well as some summarization of any code edits that you make.
- For viewing the code edits the user will view them in a separate code editor window, and the back-and-forth
- between the chat and the code editor should be minimized as well as facilitated by you.
- If complex changes have been made, advise the user on how to review them in the code editor.
- If complex relationships that the user asked for should be visualized or explained, consider creating
- a diagram in addition to your text-based communication. Note that in the chat interface you have various rendering
- options for text, html, and mermaid diagrams, as has been explained to you in your initial instructions.
+ To view the code edits you make, the user will have switch to a separate application.
+ To illustrate complex relationships, consider creating diagrams in addition to your text-based communication
+ (depending on the options for text, html, mermaid diagrams, etc. that you are provided with in your initial instructions).
excluded_tools: []
included_optional_tools:
- switch_modes
diff --git a/src/serena/resources/config/contexts/ide-assistant.yml b/src/serena/resources/config/contexts/ide-assistant.yml
deleted file mode 100644
index 8d64541c..00000000
--- a/src/serena/resources/config/contexts/ide-assistant.yml
+++ /dev/null
@@ -1,34 +0,0 @@
-description: Non-symbolic editing tools and general shell tool are excluded
-prompt: |
- You are running in IDE assistant context where file operations, basic (line-based) edits and reads,
- and shell commands are handled by your own, internal tools.
- The initial instructions and the current config inform you on which tools are available to you,
- and how to use them.
- Don't attempt to use any excluded tools, instead rely on your own internal tools
- for achieving the basic file or shell operations.
-
- If serena's tools can be used for achieving your task,
- you should prioritize them. In particular, it is important that you avoid reading entire source code files,
- unless it is strictly necessary! Instead, for exploring and reading code in a token-efficient manner,
- you should use serena's overview and symbolic search tools.
- Before reading a full file, it is usually best to first explore the file using the symbol_overview tool,
- and then make targeted reads using find_symbol and other symbolic tools.
- For non-code files or for reads where you don't know the symbol's name path, you can use the pattern searching tool,
- and read full files only as a last resort.
-
-excluded_tools:
- - create_text_file
- - read_file
- - execute_shell_command
- - prepare_for_new_conversation
- - replace_content
-
-tool_description_overrides: {}
-
-# whether to assume that Serena shall only work on a single project in this context (provided that a project is given
-# when Serena is started).
-# If set to true and 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.
-# The `activate_project` tool is always disabled in this case, as project switching cannot be allowed.
-single_project: true
diff --git a/src/serena/resources/config/contexts/ide.yml b/src/serena/resources/config/contexts/ide.yml
new file mode 100644
index 00000000..4a917a56
--- /dev/null
+++ b/src/serena/resources/config/contexts/ide.yml
@@ -0,0 +1,25 @@
+description: Generic IDE coding agent context (basic file operations and shell operations assumed to be covered; single project mode)
+prompt: |
+ You are running in an IDE assistant context where file operations, basic (line-based) edits and reads,
+ and shell commands are handled by your own, internal tools.
+
+ If Serena's tools can be used to achieve your task, you should prioritize them.
+ In particular, it is important that you avoid reading entire source code files unless it is strictly necessary!
+ Instead, for exploring and reading code in a token-efficient manner, use Serena's overview and symbolic search tools.
+ For non-code files or for reads where you don't know the symbol's name path, you can use the pattern search tool.
+
+excluded_tools:
+ - create_text_file
+ - read_file
+ - execute_shell_command
+ - prepare_for_new_conversation
+
+tool_description_overrides: {}
+
+# whether to assume that Serena shall only work on a single project in this context (provided that a project is given
+# when Serena is started).
+# If set to true and 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.
+# The `activate_project` tool is always disabled in this case, as project switching cannot be allowed.
+single_project: true
diff --git a/src/serena/resources/config/modes/editing.yml b/src/serena/resources/config/modes/editing.yml
index ba468191..4de188b2 100644
--- a/src/serena/resources/config/modes/editing.yml
+++ b/src/serena/resources/config/modes/editing.yml
@@ -1,39 +1,31 @@
description: All tools, with detailed instructions for code editing
prompt: |
- You are operating in editing mode. You can edit files with the provided tools
- to implement the requested changes to the code base while adhering to the project's code style and patterns.
+ You are operating in editing mode. You can edit files with the provided tools.
+ You adhere to the project's code style and patterns.
+
Use symbolic editing tools whenever possible for precise code modifications.
- If no editing task has yet been provided, wait for the user to provide one.
+ If no explicit editing task has yet been provided, wait for the user to provide one. Do not be overly eager.
When writing new code, think about where it belongs best. Don't generate new files if you don't plan on actually
- integrating them into the codebase, instead use the editing tools to insert the code directly into the existing files in that case.
+ properly integrating them into the codebase.
- You have two main approaches for editing code - editing at the symbol level and more file-based editing.
+ You have two main approaches for editing code: (a) editing at the symbol level and (b) file-based editing.
The symbol-based approach is appropriate if you need to adjust an entire symbol, e.g. a method, a class, a function, etc.
- But it is not appropriate if you need to adjust just a few lines of code within a symbol.
+ It is not appropriate if you need to adjust just a few lines of code within a larger symbol.
- Let us first discuss the symbol-based approach.
- Symbols are identified by their name path and relative file path (description of the `find_symbol` tool).
- You can get information about available symbols by using the `get_symbols_overview` tool for finding top-level symbols in a file,
- or by using `find_symbol` if you already know the symbol's name path. You generally try to read as little code as possible
- while still solving your task, meaning you only read the bodies when you need to, and after you have found the symbol you want to edit.
- Before calling symbolic reading tools, you should have a basic understanding of the repository structure that you can get from memories
- or by using the `list_dir` and `find_file` tools (or similar).
- For example, if you are working with python code and already know that you need to read the body of the constructor of the class Foo, you can directly
- use `find_symbol` with the name path `Foo/__init__` and `include_body=True`. If you don't know yet which methods in `Foo` you need to read or edit,
- you can use `find_symbol` with the name path `Foo`, `include_body=False` and `depth=1` to get all (top-level) methods of `Foo` before proceeding
- to read the desired methods with `include_body=True`.
- In particular, keep in mind the description of the `replace_symbol_body` tool. If you want to add some new code at the end of the file, you should
- use the `insert_after_symbol` tool with the last top-level symbol in the file. If you want to add an import, often a good strategy is to use
- `insert_before_symbol` with the first top-level symbol in the file.
- You can understand relationships between symbols by using the `find_referencing_symbols` tool. If not explicitly requested otherwise by a user,
- you make sure that when you edit a symbol, it is either done in a backward-compatible way, or you find and adjust the references as needed.
- The `find_referencing_symbols` tool will give you code snippets around the references, as well as symbolic information.
- You will generally be able to use the info from the snippets and the regex-based approach to adjust the references as well.
- You can assume that all symbol editing tools are reliable, so you don't need to verify the results if the tool returns without error.
+ **Symbolic editing**
+ Use symbolic retrieval tools to identify the symbols you need to edit.
+ If you need to replace the definition of a symbol, use the `replace_symbol_body` tool.
+ If you want to add some new code at the end of the file, use the `insert_after_symbol` tool with the last top-level symbol in the file.
+ Similarly, you can use `insert_before_symbol` with the first top-level symbol in the file to insert code at the beginning of a file.
+ You can understand relationships between symbols by using the `find_referencing_symbols` tool. If not explicitly requested otherwise by the user,
+ you make sure that when you edit a symbol, the change is either backward-compatible or you find and update all references as needed.
+ The `find_referencing_symbols` tool will give you code snippets around the references as well as symbolic information.
+ You can assume that all symbol editing tools are reliable, so you never need to verify the results if the tools return without error.
{% if 'replace_content' in available_tools %}
- The `replace_content` tool allows you to perform regex-based replacements within files.
+ **File-based editing**
+ The `replace_content` tool allows you to perform regex-based replacements within files (as well as simple string replacements).
This is your primary tool for editing code whenever replacing or deleting a whole symbol would be a more expensive operation,
e.g. if you need to adjust just a few lines of code within a method.
You are extremely good at regex, so you never need to check whether the replacement produced the correct result.
diff --git a/src/serena/resources/config/modes/interactive.yml b/src/serena/resources/config/modes/interactive.yml
index 4da123d7..cdae4e99 100644
--- a/src/serena/resources/config/modes/interactive.yml
+++ b/src/serena/resources/config/modes/interactive.yml
@@ -6,6 +6,6 @@ prompt: |
Break down complex tasks into smaller steps and explain your thinking at each stage. When you're uncertain about
a decision, present options to the user and ask for guidance rather than making assumptions.
- Focus on providing informative results for intermediate steps so the user can follow along with your progress and
+ Focus on providing informative results for intermediate steps, such that the user can follow along with your progress and
provide feedback as needed.
excluded_tools: []
\ No newline at end of file
diff --git a/src/serena/resources/config/modes/mode.template.yml b/src/serena/resources/config/modes/mode.template.yml
index 6d836ca3..d7efce0d 100644
--- a/src/serena/resources/config/modes/mode.template.yml
+++ b/src/serena/resources/config/modes/mode.template.yml
@@ -1,7 +1,8 @@
# See Serena's documentation for more details on concept of modes.
-description: Description of the mode, not used in the code.
-prompt: Prompt that will form part of the message sent to the model when activating this mode.
+description: Description of the mode (meta-information only)
+prompt: |
+ Provide a prompt that will form part of the instructions sent to the model when this mode is activated.
+# tools that are to be excluded by this mode
excluded_tools: []
-
# several tools are excluded by default and have to be explicitly included by the user
included_optional_tools: []
\ No newline at end of file
diff --git a/src/serena/resources/config/modes/no-memories.yml b/src/serena/resources/config/modes/no-memories.yml
new file mode 100644
index 00000000..7a522875
--- /dev/null
+++ b/src/serena/resources/config/modes/no-memories.yml
@@ -0,0 +1,11 @@
+description: Excludes Serena's memory tools (and onboarding tools, which rely on memory)
+prompt: |
+ Serena's memory tools are not available and the onboarding workflow is not being applied.
+excluded_tools:
+ - write_memory
+ - read_memory
+ - delete_memory
+ - edit_memory
+ - list_memories
+ - onboarding
+ - check_onboarding_performed
diff --git a/src/serena/resources/config/modes/no-onboarding.yml b/src/serena/resources/config/modes/no-onboarding.yml
index e4d15cc7..4b893eff 100644
--- a/src/serena/resources/config/modes/no-onboarding.yml
+++ b/src/serena/resources/config/modes/no-onboarding.yml
@@ -1,8 +1,6 @@
-description: Onboarding was already performed, exclude all onboarding tools
+description: The onboarding process is not used (memories may have been created externally)
prompt: |
- You have already performed onboarding, meaning that memories have already been created.
- Read a list of available memories using the `list_memories` tool before proceeding with the task.
- You don't need to read the actual memories, just remember that they exist and that you can read them later if they are relevant for your task.
+ The onboarding process is not applied.
excluded_tools:
- onboarding
- check_onboarding_performed
diff --git a/src/serena/resources/config/modes/planning.yml b/src/serena/resources/config/modes/planning.yml
index ce0bf2e1..a24d0dfb 100644
--- a/src/serena/resources/config/modes/planning.yml
+++ b/src/serena/resources/config/modes/planning.yml
@@ -1,8 +1,7 @@
description: Only read-only tools, focused on analysis and planning
prompt: |
You are operating in planning mode. Your task is to analyze code but not write any code.
- The user may ask you to assist in creating a comprehensive plan, or to learn something about the codebase -
- either a small aspect of it or about the whole project.
+ The user may ask you to assist in creating a comprehensive plan, or to learn something about the codebase.
excluded_tools:
- create_text_file
- replace_symbol_body
diff --git a/src/serena/resources/config/prompt_templates/system_prompt.yml b/src/serena/resources/config/prompt_templates/system_prompt.yml
index ce47c3e5..13064c14 100644
--- a/src/serena/resources/config/prompt_templates/system_prompt.yml
+++ b/src/serena/resources/config/prompt_templates/system_prompt.yml
@@ -3,66 +3,52 @@
# is encouraged (via the tool description) to call at the beginning of the conversation.
prompts:
system_prompt: |
- You are a professional coding agent concerned with one particular codebase. You have
- access to semantic coding tools on which you rely heavily for all your work, as well as collection of memory
- files containing general information about the codebase. You operate in a resource-efficient and intelligent manner, always
- keeping in mind to not read or generate content that is not needed for the task at hand.
+ You are a professional coding agent.
+ You have access to semantic coding tools upon which you rely heavily for all your work.
+ You operate in a resource-efficient and intelligent manner, always keeping in mind to not read or generate
+ content that is not needed for the task at hand.
- When reading code in order to answer a user question or task, you should try reading only the necessary code.
Some tasks may require you to understand the architecture of large parts of the codebase, while for others,
it may be enough to read a small set of symbols or a single file.
- Generally, you should avoid reading entire files unless it is absolutely necessary, instead relying on
- intelligent step-by-step acquisition of information. {% if 'ToolMarkerSymbolicRead' in available_markers %}However, if you already read a file, it does not make
- sense to further analyse it with the symbolic tools (except for the `find_referencing_symbols` tool),
- as you already have the information.{% endif %}
+ You avoid reading entire files unless it is absolutely necessary, instead relying on intelligent step-by-step
+ acquisition of information. {% if 'ToolMarkerSymbolicRead' in available_markers %}Once you have read a full file, it does not make
+ sense to analyse it with the symbolic read tools; you already have the information.{% endif %}
- I WILL BE SERIOUSLY UPSET IF YOU READ ENTIRE FILES WITHOUT NEED!
- {% if 'ToolMarkerSymbolicRead' in available_markers %}
- CONSIDER INSTEAD USING THE OVERVIEW TOOL AND SYMBOLIC TOOLS TO READ ONLY THE NECESSARY CODE FIRST!
- I WILL BE EVEN MORE UPSET IF AFTER HAVING READ AN ENTIRE FILE YOU KEEP READING THE SAME CONTENT WITH THE SYMBOLIC TOOLS!
- THE PURPOSE OF THE SYMBOLIC TOOLS IS TO HAVE TO READ LESS CODE, NOT READ THE SAME CONTENT MULTIPLE TIMES!
- {% endif %}
-
- You can achieve the intelligent reading of code by using the symbolic tools for getting an overview of symbols and
- the relations between them, and then only reading the bodies of symbols that are necessary to answer the question
- or complete the task.
+ You can achieve intelligent reading of code by using the symbolic tools for getting an overview of symbols and
+ the relations between them, and then only reading the bodies of symbols that are necessary to complete the task at hand.
You can use the standard tools like list_dir, find_file and search_for_pattern if you need to.
- When tools allow it, you pass the `relative_path` parameter to restrict the search to a specific file or directory.
- For some tools, `relative_path` can only be a file path, so make sure to properly read the tool descriptions.
+ Where appropriate, you pass the `relative_path` parameter to restrict the search to a specific file or directory.
{% if 'search_for_pattern' in available_tools %}
If you are unsure about a symbol's name or location{% if 'find_symbol' in available_tools %} (to the extent that substring_matching for the symbol name is not enough){% endif %}, you can use the `search_for_pattern` tool, which allows fast
- and flexible search for patterns in the codebase.{% if 'ToolMarkerSymbolicRead' in available_markers %}This way you can first find candidates for symbols or files,
+ and flexible search for patterns in the codebase.{% if 'ToolMarkerSymbolicRead' in available_markers %} In this way, you can first find candidates for symbols or files,
and then proceed with the symbolic tools.{% endif %}
{% endif %}
{% if 'ToolMarkerSymbolicRead' in available_markers %}
- Symbols are identified by their `name_path and `relative_path`, see the description of the `find_symbol` tool for more details
- on how the `name_path` matches symbols.
- You can get information about available symbols by using the `get_symbols_overview` tool for finding top-level symbols in a file,
- or by using `find_symbol` if you already know the symbol's name path. You generally try to read as little code as possible
- while still solving your task, meaning you only read the bodies when you need to, and after you have found the symbol you want to edit.
- For example, if you are working with python code and already know that you need to read the body of the constructor of the class Foo, you can directly
- use `find_symbol` with the name path `Foo/__init__` and `include_body=True`. If you don't know yet which methods in `Foo` you need to read or edit,
- you can use `find_symbol` with the name path `Foo`, `include_body=False` and `depth=1` to get all (top-level) methods of `Foo` before proceeding
- to read the desired methods with `include_body=True`
+ Symbols are identified by their `name_path` and `relative_path` (see the description of the `find_symbol` tool).
+ You can get information about the symbols in a file by using the `get_symbols_overview` tool or use the `find_symbol` to search.
+ You only read the bodies of symbols when you need to (e.g. if you want to fully understand or edit it).
+ For example, if you are working with Python code and already know that you need to read the body of the constructor of the class Foo, you can directly
+ use `find_symbol` with name path pattern `Foo/__init__` and `include_body=True`. If you don't know yet which methods in `Foo` you need to read or edit,
+ you can use `find_symbol` with name path pattern `Foo`, `include_body=False` and `depth=1` to get all (top-level) methods of `Foo` before proceeding
+ to read the desired methods with `include_body=True`.
You can understand relationships between symbols by using the `find_referencing_symbols` tool.
{% endif %}
{% if 'read_memory' in available_tools %}
- You generally have access to memories and it may be useful for you to read them, but also only if they help you
- to answer the question or complete the task. You can infer which memories are relevant to the current task by reading
- the memory names and descriptions.
+ You generally have access to memories and it may be useful for you to read them.
+ You infer whether memories are relevant based on their names.
{% endif %}
- The context and modes of operation are described below. From them you can infer how to interact with your user
- and which tasks and kinds of interactions are expected of you.
+ The context and modes of operation are described below. These determine how to interact with your user
+ and which kinds of interactions are expected of you.
Context description:
{{ context_system_prompt }}
Modes descriptions:
{% for prompt in mode_system_prompts %}
- - {{ prompt }}
+ {{ prompt }}
{% endfor %}
You have hereby read the 'Serena Instructions Manual' and do not need to read it again.
diff --git a/src/serena/resources/dashboard/dashboard.css b/src/serena/resources/dashboard/dashboard.css
index 8795b895..1bcc7a23 100644
--- a/src/serena/resources/dashboard/dashboard.css
+++ b/src/serena/resources/dashboard/dashboard.css
@@ -97,11 +97,18 @@ body {
}
.logo-container {
+ height: var(--header-height);
order: 1;
flex-shrink: 0;
}
-.header-sponsor {
+.logo-container img {
+ height: calc(var(--header-height) - 20px);
+ margin-top: 10px;
+ display: block;
+}
+
+.header-banner {
position: relative;
top: 0;
left: 0;
@@ -109,11 +116,6 @@ body {
max-height: var(--header-height);
}
-.logo-container img {
- height: var(--header-height);
- display: block;
-}
-
.header-nav {
position: relative;
display: flex;
@@ -178,7 +180,7 @@ body {
margin: 5px 0;
}
-.platinum-sponsor-slide {
+.platinum-banner-slide {
position: absolute;
top: 0;
left: 0;
@@ -188,26 +190,26 @@ body {
transition: opacity 0.5s ease-in-out;
}
-.platinum-sponsor-slide.active {
+.platinum-banner-slide.active {
opacity: 1;
pointer-events: auto;
}
-.sponsor-image {
+.banner-image {
object-fit: contain;
border-radius: var(--border-radius);
}
-.sponsor-border {
+.banner-border {
border: 1px solid var(--border-color);
}
-.platinum-sponsor-slide .sponsor-image {
+.platinum-banner-slide .banner-image {
max-height: 100%;
object-fit: contain;
}
-.gold-sponsors-section {
+.gold-banners-section {
margin: 0 auto;
width: 100%;
position: relative;
@@ -223,18 +225,19 @@ body {
overflow: hidden;
}
-.gold-sponsor-slide {
+.gold-banner-slide {
position: absolute;
+ pointer-events: none;
opacity: 0;
transition: opacity 0.5s ease-in-out;
}
-.gold-sponsor-slide.active {
+.gold-banner-slide.active {
opacity: 1;
pointer-events: auto;
}
-.gold-sponsor-slide .sponsor-image {
+.gold-banner-slide .banner-image {
max-width: 100%;
object-fit: contain;
}
diff --git a/src/serena/resources/dashboard/dashboard.js b/src/serena/resources/dashboard/dashboard.js
index ef8168fa..63a98419 100644
--- a/src/serena/resources/dashboard/dashboard.js
+++ b/src/serena/resources/dashboard/dashboard.js
@@ -40,56 +40,74 @@ class LogMessage {
};
}
-class SponsorRotation {
+function updateThemeAwareImage($img, theme=null) {
+ if (!theme) {
+ const isDarkMode = $('html').data("theme") == 'dark';
+ theme = isDarkMode ? 'dark' : 'light';
+ }
+ console.log("updating theme-aware image to theme:", theme);
+ const newSrc = $img.data('src-' + theme);
+ if (newSrc) {
+ $img.attr('src', newSrc);
+ }
+}
+
+class BannerRotation {
constructor() {
this.platinumIndex = 0;
this.goldIndex = 0;
this.platinumTimer = null;
this.goldTimer = null;
- this.platinumInterval = 5000;
- this.goldInterval = 5000;
+ this.platinumInterval = 15000;
+ this.goldInterval = 15000;
this.init();
}
init() {
let self = this;
- this.loadSponsors(function() {
+ this.loadBanners(function() {
self.startPlatinumRotation();
self.startGoldRotation();
});
}
- loadSponsors(onSuccess) {
+ loadBanners(onSuccess) {
$.ajax({
- url: 'https://oraios-software.de/serena-sponsors/manifest.php',
+ url: 'https://oraios-software.de/serena-banners/manifest.php',
type: 'GET',
success: function (response) {
- console.log('Sponsors loaded:', response);
+ console.log('Banners loaded:', response);
- function fillSponsors($container, sponsors, className) {
- $.each(sponsors, function (index, sponsor) {
- let $img = $('');
- let $anchor = $('');
+ function fillBanners($container, banners, className) {
+ $.each(banners, function (index, banner) {
+ let $img = $('
');
+ if (banner.image_dark) {
+ $img.addClass('theme-aware-img');
+ $img.attr('data-src-dark', banner.image_dark);
+ $img.attr('data-src-light', banner.image);
+ updateThemeAwareImage($img);
+ }
+ let $anchor = $('');
$anchor.append($img);
- let $sponsor = $('');
- $sponsor.append($anchor);
+ let $banner = $('');
+ $banner.append($anchor);
if (index === 0) {
- $sponsor.addClass('active');
+ $banner.addClass('active');
}
- if (sponsor.border) {
- $img.addClass('sponsor-border');
+ if (banner.border) {
+ $img.addClass('banner-border');
}
- $container.append($sponsor);
+ $container.append($banner);
});
}
- fillSponsors($('#gold-sponsors'), response.gold, 'gold-sponsor');
- fillSponsors($('#platinum-sponsors'), response.platinum, 'platinum-sponsor');
+ fillBanners($('#gold-banners'), response.gold, 'gold-banner');
+ fillBanners($('#platinum-banners'), response.platinum, 'platinum-banner');
onSuccess();
},
error: function (xhr, status, error) {
- console.error('Error loading sponsors:', error);
+ console.error('Error loading banners:', error);
}
});
}
@@ -109,7 +127,7 @@ class SponsorRotation {
}
rotatePlatinum(direction) {
- const $slides = $('.platinum-sponsor-slide');
+ const $slides = $('.platinum-banner-slide');
const total = $slides.length;
if (total === 0) return;
@@ -133,7 +151,7 @@ class SponsorRotation {
}
rotateGold(direction) {
- const $groups = $('.gold-sponsor-slide');
+ const $groups = $('.gold-banner-slide');
const total = $groups.length;
if (total === 0) return;
@@ -360,8 +378,8 @@ class Dashboard {
// Initialize theme
this.initializeTheme();
- // Initialize sponsor rotation
- //this.sponsorRotation = new SponsorRotation();
+ // Initialize banner rotation
+ this.bannerRotation = new BannerRotation();
// Add ESC key handler for closing modals
$(document).keydown(function (e) {
@@ -1463,6 +1481,9 @@ class Dashboard {
this.setTheme(newTheme);
}
+ /**
+ * @param theme {'light' | 'dark'}
+ */
setTheme(theme) {
// Set the theme on the document element
document.documentElement.setAttribute('data-theme', theme);
@@ -1476,8 +1497,11 @@ class Dashboard {
this.$themeText.text('Dark');
}
- // Update the logo based on theme
- this.updateLogo(theme);
+ // Update theme-aware images
+ $(".theme-aware-img").each(function() {
+ const $img = $(this);
+ updateThemeAwareImage($img, theme);
+ });
// Save to localStorage
localStorage.setItem('serena-theme', theme);
@@ -1486,17 +1510,6 @@ class Dashboard {
this.updateChartsTheme();
}
- updateLogo(theme) {
- const logoElement = document.getElementById('serena-logo');
- if (logoElement) {
- if (theme === 'dark') {
- logoElement.src = 'serena-logo-dark-mode.svg';
- } else {
- logoElement.src = 'serena-logo.svg';
- }
- }
- }
-
updateChartsTheme() {
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
const textColor = isDark ? '#ffffff' : '#000000';
diff --git a/src/serena/resources/dashboard/index.html b/src/serena/resources/dashboard/index.html
index 8937051e..a8349e14 100644
--- a/src/serena/resources/dashboard/index.html
+++ b/src/serena/resources/dashboard/index.html
@@ -21,11 +21,11 @@
-
-