#
tokens: 49377/50000 51/410 files (page 2/21)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 2 of 21. Use http://codebase.md/oraios/serena?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .devcontainer
│   └── devcontainer.json
├── .dockerignore
├── .env.example
├── .github
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── config.yml
│   │   ├── feature_request.md
│   │   └── issue--bug--performance-problem--question-.md
│   └── workflows
│       ├── codespell.yml
│       ├── docker.yml
│       ├── docs.yaml
│       ├── junie.yml
│       ├── publish.yml
│       └── pytest.yml
├── .gitignore
├── .serena
│   ├── .gitignore
│   ├── memories
│   │   ├── adding_new_language_support_guide.md
│   │   ├── serena_core_concepts_and_architecture.md
│   │   ├── serena_repository_structure.md
│   │   └── suggested_commands.md
│   └── project.yml
├── .vscode
│   └── settings.json
├── CHANGELOG.md
├── CLAUDE.md
├── compose.yaml
├── CONTRIBUTING.md
├── docker_build_and_run.sh
├── DOCKER.md
├── Dockerfile
├── docs
│   ├── _config.yml
│   ├── _static
│   │   └── images
│   │       └── jetbrains-marketplace-button.png
│   ├── .gitignore
│   ├── 01-about
│   │   ├── 000_intro.md
│   │   ├── 010_llm-integration.md
│   │   ├── 020_programming-languages.md
│   │   ├── 030_serena-in-action.md
│   │   ├── 035_tools.md
│   │   ├── 040_comparison-to-other-agents.md
│   │   └── 050_acknowledgements.md
│   ├── 02-usage
│   │   ├── 000_intro.md
│   │   ├── 010_prerequisites.md
│   │   ├── 020_running.md
│   │   ├── 025_jetbrains_plugin.md
│   │   ├── 030_clients.md
│   │   ├── 040_workflow.md
│   │   ├── 050_configuration.md
│   │   ├── 060_dashboard.md
│   │   ├── 070_security.md
│   │   └── 999_additional-usage.md
│   ├── 03-special-guides
│   │   ├── 000_intro.md
│   │   ├── custom_agent.md
│   │   ├── groovy_setup_guide_for_serena.md
│   │   ├── scala_setup_guide_for_serena.md
│   │   └── serena_on_chatgpt.md
│   ├── autogen_rst.py
│   ├── create_toc.py
│   └── index.md
├── flake.lock
├── flake.nix
├── lessons_learned.md
├── LICENSE
├── llms-install.md
├── pyproject.toml
├── README.md
├── repo_dir_sync.py
├── resources
│   ├── jetbrains-marketplace-button.cdr
│   ├── serena-icons.cdr
│   ├── serena-logo-dark-mode.svg
│   ├── serena-logo.cdr
│   ├── serena-logo.svg
│   └── vscode_sponsor_logo.png
├── roadmap.md
├── scripts
│   ├── agno_agent.py
│   ├── demo_run_tools.py
│   ├── gen_prompt_factory.py
│   ├── mcp_server.py
│   ├── print_mode_context_options.py
│   ├── print_tool_overview.py
│   └── profile_tool_call.py
├── src
│   ├── interprompt
│   │   ├── __init__.py
│   │   ├── .syncCommitId.remote
│   │   ├── .syncCommitId.this
│   │   ├── jinja_template.py
│   │   ├── multilang_prompt.py
│   │   ├── prompt_factory.py
│   │   └── util
│   │       ├── __init__.py
│   │       └── class_decorators.py
│   ├── README.md
│   ├── serena
│   │   ├── __init__.py
│   │   ├── agent.py
│   │   ├── agno.py
│   │   ├── analytics.py
│   │   ├── cli.py
│   │   ├── code_editor.py
│   │   ├── config
│   │   │   ├── __init__.py
│   │   │   ├── context_mode.py
│   │   │   └── serena_config.py
│   │   ├── constants.py
│   │   ├── dashboard.py
│   │   ├── generated
│   │   │   └── generated_prompt_factory.py
│   │   ├── gui_log_viewer.py
│   │   ├── ls_manager.py
│   │   ├── mcp.py
│   │   ├── project.py
│   │   ├── prompt_factory.py
│   │   ├── resources
│   │   │   ├── config
│   │   │   │   ├── contexts
│   │   │   │   │   ├── agent.yml
│   │   │   │   │   ├── chatgpt.yml
│   │   │   │   │   ├── claude-code.yml
│   │   │   │   │   ├── codex.yml
│   │   │   │   │   ├── context.template.yml
│   │   │   │   │   ├── desktop-app.yml
│   │   │   │   │   ├── ide.yml
│   │   │   │   │   └── oaicompat-agent.yml
│   │   │   │   ├── internal_modes
│   │   │   │   │   └── jetbrains.yml
│   │   │   │   ├── modes
│   │   │   │   │   ├── editing.yml
│   │   │   │   │   ├── interactive.yml
│   │   │   │   │   ├── mode.template.yml
│   │   │   │   │   ├── no-memories.yml
│   │   │   │   │   ├── no-onboarding.yml
│   │   │   │   │   ├── onboarding.yml
│   │   │   │   │   ├── one-shot.yml
│   │   │   │   │   └── planning.yml
│   │   │   │   └── prompt_templates
│   │   │   │       ├── simple_tool_outputs.yml
│   │   │   │       └── system_prompt.yml
│   │   │   ├── dashboard
│   │   │   │   ├── dashboard.css
│   │   │   │   ├── dashboard.js
│   │   │   │   ├── index.html
│   │   │   │   ├── jquery.min.js
│   │   │   │   ├── serena-icon-16.png
│   │   │   │   ├── serena-icon-32.png
│   │   │   │   ├── serena-icon-48.png
│   │   │   │   ├── serena-logo-dark-mode.svg
│   │   │   │   ├── serena-logo.svg
│   │   │   │   ├── serena-logs-dark-mode.png
│   │   │   │   └── serena-logs.png
│   │   │   ├── project.template.yml
│   │   │   └── serena_config.template.yml
│   │   ├── symbol.py
│   │   ├── task_executor.py
│   │   ├── text_utils.py
│   │   ├── tools
│   │   │   ├── __init__.py
│   │   │   ├── cmd_tools.py
│   │   │   ├── config_tools.py
│   │   │   ├── file_tools.py
│   │   │   ├── jetbrains_plugin_client.py
│   │   │   ├── jetbrains_tools.py
│   │   │   ├── memory_tools.py
│   │   │   ├── symbol_tools.py
│   │   │   ├── tools_base.py
│   │   │   └── workflow_tools.py
│   │   └── util
│   │       ├── class_decorators.py
│   │       ├── cli_util.py
│   │       ├── exception.py
│   │       ├── file_system.py
│   │       ├── general.py
│   │       ├── git.py
│   │       ├── gui.py
│   │       ├── inspection.py
│   │       ├── logging.py
│   │       ├── shell.py
│   │       └── thread.py
│   └── solidlsp
│       ├── __init__.py
│       ├── .gitignore
│       ├── language_servers
│       │   ├── al_language_server.py
│       │   ├── bash_language_server.py
│       │   ├── clangd_language_server.py
│       │   ├── clojure_lsp.py
│       │   ├── common.py
│       │   ├── csharp_language_server.py
│       │   ├── dart_language_server.py
│       │   ├── eclipse_jdtls.py
│       │   ├── elixir_tools
│       │   │   ├── __init__.py
│       │   │   ├── elixir_tools.py
│       │   │   └── README.md
│       │   ├── elm_language_server.py
│       │   ├── erlang_language_server.py
│       │   ├── fortran_language_server.py
│       │   ├── fsharp_language_server.py
│       │   ├── gopls.py
│       │   ├── groovy_language_server.py
│       │   ├── haskell_language_server.py
│       │   ├── intelephense.py
│       │   ├── jedi_server.py
│       │   ├── julia_server.py
│       │   ├── kotlin_language_server.py
│       │   ├── lua_ls.py
│       │   ├── marksman.py
│       │   ├── matlab_language_server.py
│       │   ├── nixd_ls.py
│       │   ├── omnisharp
│       │   │   ├── initialize_params.json
│       │   │   ├── runtime_dependencies.json
│       │   │   └── workspace_did_change_configuration.json
│       │   ├── omnisharp.py
│       │   ├── pascal_server.py
│       │   ├── perl_language_server.py
│       │   ├── powershell_language_server.py
│       │   ├── pyright_server.py
│       │   ├── r_language_server.py
│       │   ├── regal_server.py
│       │   ├── ruby_lsp.py
│       │   ├── rust_analyzer.py
│       │   ├── scala_language_server.py
│       │   ├── solargraph.py
│       │   ├── sourcekit_lsp.py
│       │   ├── taplo_server.py
│       │   ├── terraform_ls.py
│       │   ├── typescript_language_server.py
│       │   ├── vts_language_server.py
│       │   ├── vue_language_server.py
│       │   ├── yaml_language_server.py
│       │   └── zls.py
│       ├── ls_config.py
│       ├── ls_exceptions.py
│       ├── ls_handler.py
│       ├── ls_request.py
│       ├── ls_types.py
│       ├── ls_utils.py
│       ├── ls.py
│       ├── lsp_protocol_handler
│       │   ├── lsp_constants.py
│       │   ├── lsp_requests.py
│       │   ├── lsp_types.py
│       │   └── server.py
│       ├── settings.py
│       └── util
│           ├── cache.py
│           ├── subprocess_util.py
│           └── zip.py
├── sync.py
├── test
│   ├── __init__.py
│   ├── conftest.py
│   ├── resources
│   │   └── repos
│   │       ├── al
│   │       │   └── test_repo
│   │       │       ├── app.json
│   │       │       └── src
│   │       │           ├── Codeunits
│   │       │           │   ├── CustomerMgt.Codeunit.al
│   │       │           │   └── PaymentProcessorImpl.Codeunit.al
│   │       │           ├── Enums
│   │       │           │   └── CustomerType.Enum.al
│   │       │           ├── Interfaces
│   │       │           │   └── IPaymentProcessor.Interface.al
│   │       │           ├── Pages
│   │       │           │   ├── CustomerCard.Page.al
│   │       │           │   └── CustomerList.Page.al
│   │       │           ├── TableExtensions
│   │       │           │   └── Item.TableExt.al
│   │       │           └── Tables
│   │       │               └── Customer.Table.al
│   │       ├── bash
│   │       │   └── test_repo
│   │       │       ├── config.sh
│   │       │       ├── main.sh
│   │       │       └── utils.sh
│   │       ├── clojure
│   │       │   └── test_repo
│   │       │       ├── deps.edn
│   │       │       └── src
│   │       │           └── test_app
│   │       │               ├── core.clj
│   │       │               └── utils.clj
│   │       ├── csharp
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── Models
│   │       │       │   └── Person.cs
│   │       │       ├── Program.cs
│   │       │       ├── serena.sln
│   │       │       └── TestProject.csproj
│   │       ├── dart
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── lib
│   │       │       │   ├── helper.dart
│   │       │       │   ├── main.dart
│   │       │       │   └── models.dart
│   │       │       └── pubspec.yaml
│   │       ├── elixir
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── lib
│   │       │       │   ├── examples.ex
│   │       │       │   ├── ignored_dir
│   │       │       │   │   └── ignored_module.ex
│   │       │       │   ├── models.ex
│   │       │       │   ├── services.ex
│   │       │       │   ├── test_repo.ex
│   │       │       │   └── utils.ex
│   │       │       ├── mix.exs
│   │       │       ├── mix.lock
│   │       │       ├── scripts
│   │       │       │   └── build_script.ex
│   │       │       └── test
│   │       │           ├── models_test.exs
│   │       │           └── test_repo_test.exs
│   │       ├── elm
│   │       │   └── test_repo
│   │       │       ├── elm.json
│   │       │       ├── Main.elm
│   │       │       └── Utils.elm
│   │       ├── erlang
│   │       │   └── test_repo
│   │       │       ├── hello.erl
│   │       │       ├── ignored_dir
│   │       │       │   └── ignored_module.erl
│   │       │       ├── include
│   │       │       │   ├── records.hrl
│   │       │       │   └── types.hrl
│   │       │       ├── math_utils.erl
│   │       │       ├── rebar.config
│   │       │       ├── src
│   │       │       │   ├── app.erl
│   │       │       │   ├── models.erl
│   │       │       │   ├── services.erl
│   │       │       │   └── utils.erl
│   │       │       └── test
│   │       │           ├── models_tests.erl
│   │       │           └── utils_tests.erl
│   │       ├── fortran
│   │       │   └── test_repo
│   │       │       ├── main.f90
│   │       │       └── modules
│   │       │           ├── geometry.f90
│   │       │           └── math_utils.f90
│   │       ├── fsharp
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── Calculator.fs
│   │       │       ├── Models
│   │       │       │   └── Person.fs
│   │       │       ├── Program.fs
│   │       │       ├── README.md
│   │       │       └── TestProject.fsproj
│   │       ├── go
│   │       │   └── test_repo
│   │       │       └── main.go
│   │       ├── groovy
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── build.gradle
│   │       │       └── src
│   │       │           └── main
│   │       │               └── groovy
│   │       │                   └── com
│   │       │                       └── example
│   │       │                           ├── Main.groovy
│   │       │                           ├── Model.groovy
│   │       │                           ├── ModelUser.groovy
│   │       │                           └── Utils.groovy
│   │       ├── haskell
│   │       │   └── test_repo
│   │       │       ├── app
│   │       │       │   └── Main.hs
│   │       │       ├── haskell-test-repo.cabal
│   │       │       ├── package.yaml
│   │       │       ├── src
│   │       │       │   ├── Calculator.hs
│   │       │       │   └── Helper.hs
│   │       │       └── stack.yaml
│   │       ├── java
│   │       │   └── test_repo
│   │       │       ├── pom.xml
│   │       │       └── src
│   │       │           └── main
│   │       │               └── java
│   │       │                   └── test_repo
│   │       │                       ├── Main.java
│   │       │                       ├── Model.java
│   │       │                       ├── ModelUser.java
│   │       │                       └── Utils.java
│   │       ├── julia
│   │       │   └── test_repo
│   │       │       ├── lib
│   │       │       │   └── helper.jl
│   │       │       └── main.jl
│   │       ├── kotlin
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── build.gradle.kts
│   │       │       └── src
│   │       │           └── main
│   │       │               └── kotlin
│   │       │                   └── test_repo
│   │       │                       ├── Main.kt
│   │       │                       ├── Model.kt
│   │       │                       ├── ModelUser.kt
│   │       │                       └── Utils.kt
│   │       ├── lua
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── main.lua
│   │       │       ├── src
│   │       │       │   ├── calculator.lua
│   │       │       │   └── utils.lua
│   │       │       └── tests
│   │       │           └── test_calculator.lua
│   │       ├── markdown
│   │       │   └── test_repo
│   │       │       ├── api.md
│   │       │       ├── CONTRIBUTING.md
│   │       │       ├── guide.md
│   │       │       └── README.md
│   │       ├── matlab
│   │       │   └── test_repo
│   │       │       ├── Calculator.m
│   │       │       └── main.m
│   │       ├── nix
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── default.nix
│   │       │       ├── flake.nix
│   │       │       ├── lib
│   │       │       │   └── utils.nix
│   │       │       ├── modules
│   │       │       │   └── example.nix
│   │       │       └── scripts
│   │       │           └── hello.sh
│   │       ├── pascal
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── lib
│   │       │       │   └── helper.pas
│   │       │       └── main.pas
│   │       ├── perl
│   │       │   └── test_repo
│   │       │       ├── helper.pl
│   │       │       └── main.pl
│   │       ├── php
│   │       │   └── test_repo
│   │       │       ├── helper.php
│   │       │       ├── index.php
│   │       │       └── simple_var.php
│   │       ├── powershell
│   │       │   └── test_repo
│   │       │       ├── main.ps1
│   │       │       ├── PowerShellEditorServices.json
│   │       │       └── utils.ps1
│   │       ├── python
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── custom_test
│   │       │       │   ├── __init__.py
│   │       │       │   └── advanced_features.py
│   │       │       ├── examples
│   │       │       │   ├── __init__.py
│   │       │       │   └── user_management.py
│   │       │       ├── ignore_this_dir_with_postfix
│   │       │       │   └── ignored_module.py
│   │       │       ├── scripts
│   │       │       │   ├── __init__.py
│   │       │       │   └── run_app.py
│   │       │       └── test_repo
│   │       │           ├── __init__.py
│   │       │           ├── complex_types.py
│   │       │           ├── models.py
│   │       │           ├── name_collisions.py
│   │       │           ├── nested_base.py
│   │       │           ├── nested.py
│   │       │           ├── overloaded.py
│   │       │           ├── services.py
│   │       │           ├── utils.py
│   │       │           └── variables.py
│   │       ├── r
│   │       │   └── test_repo
│   │       │       ├── .Rbuildignore
│   │       │       ├── DESCRIPTION
│   │       │       ├── examples
│   │       │       │   └── analysis.R
│   │       │       ├── NAMESPACE
│   │       │       └── R
│   │       │           ├── models.R
│   │       │           └── utils.R
│   │       ├── rego
│   │       │   └── test_repo
│   │       │       ├── policies
│   │       │       │   ├── authz.rego
│   │       │       │   └── validation.rego
│   │       │       └── utils
│   │       │           └── helpers.rego
│   │       ├── ruby
│   │       │   └── test_repo
│   │       │       ├── .solargraph.yml
│   │       │       ├── examples
│   │       │       │   └── user_management.rb
│   │       │       ├── lib.rb
│   │       │       ├── main.rb
│   │       │       ├── models.rb
│   │       │       ├── nested.rb
│   │       │       ├── services.rb
│   │       │       └── variables.rb
│   │       ├── rust
│   │       │   ├── test_repo
│   │       │   │   ├── Cargo.lock
│   │       │   │   ├── Cargo.toml
│   │       │   │   └── src
│   │       │   │       ├── lib.rs
│   │       │   │       └── main.rs
│   │       │   └── test_repo_2024
│   │       │       ├── Cargo.lock
│   │       │       ├── Cargo.toml
│   │       │       └── src
│   │       │           ├── lib.rs
│   │       │           └── main.rs
│   │       ├── scala
│   │       │   ├── build.sbt
│   │       │   ├── project
│   │       │   │   ├── build.properties
│   │       │   │   ├── metals.sbt
│   │       │   │   └── plugins.sbt
│   │       │   └── src
│   │       │       └── main
│   │       │           └── scala
│   │       │               └── com
│   │       │                   └── example
│   │       │                       ├── Main.scala
│   │       │                       └── Utils.scala
│   │       ├── swift
│   │       │   └── test_repo
│   │       │       ├── Package.swift
│   │       │       └── src
│   │       │           ├── main.swift
│   │       │           └── utils.swift
│   │       ├── terraform
│   │       │   └── test_repo
│   │       │       ├── data.tf
│   │       │       ├── main.tf
│   │       │       ├── outputs.tf
│   │       │       └── variables.tf
│   │       ├── toml
│   │       │   └── test_repo
│   │       │       ├── Cargo.toml
│   │       │       ├── config.toml
│   │       │       └── pyproject.toml
│   │       ├── typescript
│   │       │   └── test_repo
│   │       │       ├── .serena
│   │       │       │   └── project.yml
│   │       │       ├── index.ts
│   │       │       ├── tsconfig.json
│   │       │       ├── use_helper.ts
│   │       │       └── ws_manager.js
│   │       ├── vue
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── index.html
│   │       │       ├── package.json
│   │       │       ├── src
│   │       │       │   ├── App.vue
│   │       │       │   ├── components
│   │       │       │   │   ├── CalculatorButton.vue
│   │       │       │   │   ├── CalculatorDisplay.vue
│   │       │       │   │   └── CalculatorInput.vue
│   │       │       │   ├── composables
│   │       │       │   │   ├── useFormatter.ts
│   │       │       │   │   └── useTheme.ts
│   │       │       │   ├── main.ts
│   │       │       │   ├── stores
│   │       │       │   │   └── calculator.ts
│   │       │       │   └── types
│   │       │       │       └── index.ts
│   │       │       ├── tsconfig.json
│   │       │       ├── tsconfig.node.json
│   │       │       └── vite.config.ts
│   │       ├── yaml
│   │       │   └── test_repo
│   │       │       ├── config.yaml
│   │       │       ├── data.yaml
│   │       │       └── services.yml
│   │       └── zig
│   │           └── test_repo
│   │               ├── .gitignore
│   │               ├── build.zig
│   │               ├── src
│   │               │   ├── calculator.zig
│   │               │   ├── main.zig
│   │               │   └── math_utils.zig
│   │               └── zls.json
│   ├── serena
│   │   ├── __init__.py
│   │   ├── __snapshots__
│   │   │   └── test_symbol_editing.ambr
│   │   ├── config
│   │   │   ├── __init__.py
│   │   │   └── test_serena_config.py
│   │   ├── test_cli_project_commands.py
│   │   ├── test_edit_marker.py
│   │   ├── test_mcp.py
│   │   ├── test_serena_agent.py
│   │   ├── test_symbol_editing.py
│   │   ├── test_symbol.py
│   │   ├── test_task_executor.py
│   │   ├── test_text_utils.py
│   │   ├── test_tool_parameter_types.py
│   │   └── util
│   │       ├── test_exception.py
│   │       └── test_file_system.py
│   └── solidlsp
│       ├── al
│       │   └── test_al_basic.py
│       ├── bash
│       │   ├── __init__.py
│       │   └── test_bash_basic.py
│       ├── clojure
│       │   ├── __init__.py
│       │   └── test_clojure_basic.py
│       ├── csharp
│       │   └── test_csharp_basic.py
│       ├── dart
│       │   ├── __init__.py
│       │   └── test_dart_basic.py
│       ├── elixir
│       │   ├── __init__.py
│       │   ├── conftest.py
│       │   ├── test_elixir_basic.py
│       │   ├── test_elixir_ignored_dirs.py
│       │   ├── test_elixir_integration.py
│       │   └── test_elixir_symbol_retrieval.py
│       ├── elm
│       │   └── test_elm_basic.py
│       ├── erlang
│       │   ├── __init__.py
│       │   ├── conftest.py
│       │   ├── test_erlang_basic.py
│       │   ├── test_erlang_ignored_dirs.py
│       │   └── test_erlang_symbol_retrieval.py
│       ├── fortran
│       │   ├── __init__.py
│       │   └── test_fortran_basic.py
│       ├── fsharp
│       │   └── test_fsharp_basic.py
│       ├── go
│       │   └── test_go_basic.py
│       ├── groovy
│       │   └── test_groovy_basic.py
│       ├── haskell
│       │   ├── __init__.py
│       │   └── test_haskell_basic.py
│       ├── java
│       │   └── test_java_basic.py
│       ├── julia
│       │   └── test_julia_basic.py
│       ├── kotlin
│       │   └── test_kotlin_basic.py
│       ├── lua
│       │   └── test_lua_basic.py
│       ├── markdown
│       │   ├── __init__.py
│       │   └── test_markdown_basic.py
│       ├── matlab
│       │   ├── __init__.py
│       │   └── test_matlab_basic.py
│       ├── nix
│       │   └── test_nix_basic.py
│       ├── pascal
│       │   ├── __init__.py
│       │   └── test_pascal_basic.py
│       ├── perl
│       │   └── test_perl_basic.py
│       ├── php
│       │   └── test_php_basic.py
│       ├── powershell
│       │   ├── __init__.py
│       │   └── test_powershell_basic.py
│       ├── python
│       │   ├── test_python_basic.py
│       │   ├── test_retrieval_with_ignored_dirs.py
│       │   └── test_symbol_retrieval.py
│       ├── r
│       │   ├── __init__.py
│       │   └── test_r_basic.py
│       ├── rego
│       │   └── test_rego_basic.py
│       ├── ruby
│       │   ├── test_ruby_basic.py
│       │   └── test_ruby_symbol_retrieval.py
│       ├── rust
│       │   ├── test_rust_2024_edition.py
│       │   ├── test_rust_analyzer_detection.py
│       │   └── test_rust_basic.py
│       ├── scala
│       │   └── test_scala_language_server.py
│       ├── swift
│       │   └── test_swift_basic.py
│       ├── terraform
│       │   └── test_terraform_basic.py
│       ├── test_lsp_protocol_handler_server.py
│       ├── toml
│       │   ├── __init__.py
│       │   ├── test_toml_basic.py
│       │   ├── test_toml_edge_cases.py
│       │   ├── test_toml_ignored_dirs.py
│       │   └── test_toml_symbol_retrieval.py
│       ├── typescript
│       │   └── test_typescript_basic.py
│       ├── util
│       │   └── test_zip.py
│       ├── vue
│       │   ├── __init__.py
│       │   ├── test_vue_basic.py
│       │   ├── test_vue_error_cases.py
│       │   ├── test_vue_rename.py
│       │   └── test_vue_symbol_retrieval.py
│       ├── yaml_ls
│       │   ├── __init__.py
│       │   └── test_yaml_basic.py
│       └── zig
│           └── test_zig_basic.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/test/resources/repos/bash/test_repo/utils.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | 
 3 | # Utility functions for bash scripting
 4 | 
 5 | # String manipulation functions
 6 | function to_uppercase() {
 7 |     echo "${1^^}"
 8 | }
 9 | 
10 | function to_lowercase() {
11 |     echo "${1,,}"
12 | }
13 | 
14 | function trim_whitespace() {
15 |     local var="$1"
16 |     var="${var#"${var%%[![:space:]]*}"}"
17 |     var="${var%"${var##*[![:space:]]}"}"   
18 |     echo "$var"
19 | }
20 | 
21 | # File operations
22 | function backup_file() {
23 |     local file="$1"
24 |     local backup_dir="${2:-./backups}"
25 |     
26 |     if [[ ! -f "$file" ]]; then
27 |         echo "Error: File '$file' does not exist" >&2
28 |         return 1
29 |     fi
30 |     
31 |     mkdir -p "$backup_dir"
32 |     cp "$file" "${backup_dir}/$(basename "$file").$(date +%Y%m%d_%H%M%S).bak"
33 |     echo "Backup created for $file"
34 | }
35 | 
36 | # Array operations
37 | function contains_element() {
38 |     local element="$1"
39 |     shift
40 |     local array=("$@")
41 |     
42 |     for item in "${array[@]}"; do
43 |         if [[ "$item" == "$element" ]]; then
44 |             return 0
45 |         fi
46 |     done
47 |     return 1
48 | }
49 | 
50 | # Logging functions
51 | function log_message() {
52 |     local level="$1"
53 |     local message="$2"
54 |     local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
55 |     
56 |     case "$level" in
57 |         "ERROR")
58 |             echo "[$timestamp] ERROR: $message" >&2
59 |             ;;
60 |         "WARN")
61 |             echo "[$timestamp] WARN: $message" >&2
62 |             ;;
63 |         "INFO")
64 |             echo "[$timestamp] INFO: $message"
65 |             ;;
66 |         "DEBUG")
67 |             [[ "${DEBUG:-false}" == "true" ]] && echo "[$timestamp] DEBUG: $message"
68 |             ;;
69 |         *)
70 |             echo "[$timestamp] $message"
71 |             ;;
72 |     esac
73 | }
74 | 
75 | # Validation functions
76 | function is_valid_email() {
77 |     local email="$1"
78 |     [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]
79 | }
80 | 
81 | function is_number() {
82 |     [[ $1 =~ ^[0-9]+$ ]]
83 | }
84 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/erlang/test_erlang_basic.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Basic integration tests for the Erlang language server functionality.
 3 | 
 4 | These tests validate the functionality of the language server APIs
 5 | like request_references using the test repository.
 6 | """
 7 | 
 8 | import pytest
 9 | 
10 | from solidlsp import SolidLanguageServer
11 | from solidlsp.ls_config import Language
12 | 
13 | from . import ERLANG_LS_UNAVAILABLE, ERLANG_LS_UNAVAILABLE_REASON
14 | 
15 | 
16 | @pytest.mark.erlang
17 | @pytest.mark.skipif(ERLANG_LS_UNAVAILABLE, reason=f"Erlang LS not available: {ERLANG_LS_UNAVAILABLE_REASON}")
18 | class TestErlangLanguageServerBasics:
19 |     """Test basic functionality of the Erlang language server."""
20 | 
21 |     @pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
22 |     def test_language_server_initialization(self, language_server: SolidLanguageServer) -> None:
23 |         """Test that the Erlang language server initializes properly."""
24 |         assert language_server is not None
25 |         assert language_server.language == Language.ERLANG
26 | 
27 |     @pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
28 |     def test_document_symbols(self, language_server: SolidLanguageServer) -> None:
29 |         """Test document symbols retrieval for Erlang files."""
30 |         try:
31 |             file_path = "hello.erl"
32 |             symbols_tuple = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
33 |             assert isinstance(symbols_tuple, tuple)
34 |             assert len(symbols_tuple) == 2
35 | 
36 |             all_symbols, root_symbols = symbols_tuple
37 |             assert isinstance(all_symbols, list)
38 |             assert isinstance(root_symbols, list)
39 |         except Exception as e:
40 |             if "not fully initialized" in str(e):
41 |                 pytest.skip("Erlang language server not fully initialized")
42 |             else:
43 |                 raise
44 | 
```

--------------------------------------------------------------------------------
/src/serena/util/general.py:
--------------------------------------------------------------------------------

```python
 1 | import os
 2 | from dataclasses import MISSING, Field
 3 | from typing import Any, Literal, cast, overload
 4 | 
 5 | from ruamel.yaml import YAML
 6 | from ruamel.yaml.comments import CommentedMap
 7 | 
 8 | from serena.constants import SERENA_FILE_ENCODING
 9 | 
10 | 
11 | def _create_YAML(preserve_comments: bool = False) -> YAML:
12 |     """
13 |     Creates a YAML that can load/save with comments if preserve_comments is True.
14 |     """
15 |     typ = None if preserve_comments else "safe"
16 |     result = YAML(typ=typ)
17 |     result.preserve_quotes = preserve_comments
18 |     return result
19 | 
20 | 
21 | @overload
22 | def load_yaml(path: str, preserve_comments: Literal[False]) -> dict: ...
23 | @overload
24 | def load_yaml(path: str, preserve_comments: Literal[True]) -> CommentedMap: ...
25 | def load_yaml(path: str, preserve_comments: bool = False) -> dict | CommentedMap:
26 |     with open(path, encoding=SERENA_FILE_ENCODING) as f:
27 |         yaml = _create_YAML(preserve_comments)
28 |         return yaml.load(f)
29 | 
30 | 
31 | def save_yaml(path: str, data: dict | CommentedMap, preserve_comments: bool = False) -> None:
32 |     yaml = _create_YAML(preserve_comments)
33 |     os.makedirs(os.path.dirname(path), exist_ok=True)
34 |     with open(path, "w", encoding=SERENA_FILE_ENCODING) as f:
35 |         yaml.dump(data, f)
36 | 
37 | 
38 | def get_dataclass_default(cls: type, field_name: str) -> Any:
39 |     """
40 |     Gets the default value of a dataclass field.
41 | 
42 |     :param cls: The dataclass type.
43 |     :param field_name: The name of the field.
44 |     :return: The default value of the field (either from default or default_factory).
45 |     """
46 |     field = cast(Field, cls.__dataclass_fields__[field_name])  # type: ignore[attr-defined]
47 | 
48 |     if field.default is not MISSING:
49 |         return field.default
50 | 
51 |     if field.default_factory is not MISSING:  # default_factory is a function
52 |         return field.default_factory()
53 | 
54 |     raise AttributeError(f"{field_name} has no default")
55 | 
```

--------------------------------------------------------------------------------
/docs/02-usage/060_dashboard.md:
--------------------------------------------------------------------------------

```markdown
 1 | # The Dashboard and GUI Tool
 2 | 
 3 | Serena comes with built-in tools for monitoring and managing the current session:
 4 | 
 5 | * the **web-based dashboard** (enabled by default)
 6 |   
 7 |   The dashboard provides detailed information on your Serena session, the current configuration and provides access to logs.
 8 |   Some settings (e.g. the current set of active programming languages) can also be directly modified through the dashboard.
 9 | 
10 |   The dashboard is supported on all platforms.
11 |   
12 |   By default, it will be accessible at `http://localhost:24282/dashboard/index.html`,
13 |   but a higher port may be used if the default port is unavailable/multiple instances are running.
14 | 
15 | * the **GUI tool** (disabled by default)
16 |   
17 |   The GUI tool is a native application window which displays logs.
18 |   It furthermore allows you to shut down the agent and to access the dashboard's URL (if it is running). 
19 | 
20 |   This is mainly supported on Windows, but it may also work on Linux; macOS is unsupported.
21 | 
22 | Both can be configured in Serena's [configuration](050_configuration) file (`serena_config.yml`).
23 | If enabled, they will automatically be opened as soon as the Serena agent/MCP server is started.
24 | For the dashboard, this can be disabled if desired (see below).
25 | 
26 | ## Disabling Automatic Browser Opening
27 | 
28 | If you prefer not to have the dashboard open automatically (e.g., to avoid focus stealing), you can disable it
29 | by setting `web_dashboard_open_on_launch: False` in your `serena_config.yml`.
30 | 
31 | When automatic opening is disabled, you can still access the dashboard by:
32 | * asking the LLM to "open the Serena dashboard", which will open the dashboard in your default browser
33 |   (the tool `open_dashboard` is enabled for this purpose, provided that the dashboard is active, 
34 |   not opened by default and the GUI tool, which can provide the URL, is not enabled)
35 | * navigating directly to the URL (see above)
36 | 
```

--------------------------------------------------------------------------------
/test/resources/repos/python/test_repo/test_repo/nested_base.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Module to test parsing of classes with nested module paths in base classes.
 3 | """
 4 | 
 5 | from typing import Generic, TypeVar
 6 | 
 7 | T = TypeVar("T")
 8 | 
 9 | 
10 | class BaseModule:
11 |     """Base module class for nested module tests."""
12 | 
13 | 
14 | class SubModule:
15 |     """Sub-module class for nested paths."""
16 | 
17 |     class NestedBase:
18 |         """Nested base class."""
19 | 
20 |         def base_method(self):
21 |             """Base method."""
22 |             return "base"
23 | 
24 |         class NestedLevel2:
25 |             """Nested level 2."""
26 | 
27 |             def nested_level_2_method(self):
28 |                 """Nested level 2 method."""
29 |                 return "nested_level_2"
30 | 
31 |     class GenericBase(Generic[T]):
32 |         """Generic nested base class."""
33 | 
34 |         def generic_method(self, value: T) -> T:
35 |             """Generic method."""
36 |             return value
37 | 
38 | 
39 | # Classes extending base classes with single-level nesting
40 | class FirstLevel(SubModule):
41 |     """Class extending a class from a nested module path."""
42 | 
43 |     def first_level_method(self):
44 |         """First level method."""
45 |         return "first"
46 | 
47 | 
48 | # Classes extending base classes with multi-level nesting
49 | class TwoLevel(SubModule.NestedBase):
50 |     """Class extending a doubly-nested base class."""
51 | 
52 |     def multi_level_method(self):
53 |         """Multi-level method."""
54 |         return "multi"
55 | 
56 |     def base_method(self):
57 |         """Override of base method."""
58 |         return "overridden"
59 | 
60 | 
61 | class ThreeLevel(SubModule.NestedBase.NestedLevel2):
62 |     """Class extending a triply-nested base class."""
63 | 
64 |     def three_level_method(self):
65 |         """Three-level method."""
66 |         return "three"
67 | 
68 | 
69 | # Class extending a generic base class with nesting
70 | class GenericExtension(SubModule.GenericBase[str]):
71 |     """Class extending a generic nested base class."""
72 | 
73 |     def generic_extension_method(self, text: str) -> str:
74 |         """Extension method."""
75 |         return f"Extended: {text}"
76 | 
```

--------------------------------------------------------------------------------
/src/serena/util/inspection.py:
--------------------------------------------------------------------------------

```python
 1 | import logging
 2 | import os
 3 | from collections.abc import Generator
 4 | from typing import TypeVar
 5 | 
 6 | from serena.util.file_system import find_all_non_ignored_files
 7 | from solidlsp.ls_config import Language
 8 | 
 9 | T = TypeVar("T")
10 | 
11 | log = logging.getLogger(__name__)
12 | 
13 | 
14 | def iter_subclasses(cls: type[T], recursive: bool = True) -> Generator[type[T], None, None]:
15 |     """Iterate over all subclasses of a class. If recursive is True, also iterate over all subclasses of all subclasses."""
16 |     for subclass in cls.__subclasses__():
17 |         yield subclass
18 |         if recursive:
19 |             yield from iter_subclasses(subclass, recursive)
20 | 
21 | 
22 | def determine_programming_language_composition(repo_path: str) -> dict[Language, float]:
23 |     """
24 |     Determine the programming language composition of a repository.
25 | 
26 |     :param repo_path: Path to the repository to analyze
27 | 
28 |     :return: Dictionary mapping languages to percentages of files matching each language
29 |     """
30 |     all_files = find_all_non_ignored_files(repo_path)
31 | 
32 |     if not all_files:
33 |         return {}
34 | 
35 |     # Count files for each language
36 |     language_counts: dict[Language, int] = {}
37 |     total_files = len(all_files)
38 | 
39 |     for language in Language.iter_all(include_experimental=False):
40 |         matcher = language.get_source_fn_matcher()
41 |         count = 0
42 | 
43 |         for file_path in all_files:
44 |             # Use just the filename for matching, not the full path
45 |             filename = os.path.basename(file_path)
46 |             if matcher.is_relevant_filename(filename):
47 |                 count += 1
48 | 
49 |         if count > 0:
50 |             language_counts[language] = count
51 | 
52 |     # Convert counts to percentages
53 |     language_percentages: dict[Language, float] = {}
54 |     for language, count in language_counts.items():
55 |         percentage = (count / total_files) * 100
56 |         language_percentages[language] = round(percentage, 2)
57 | 
58 |     return language_percentages
59 | 
```

--------------------------------------------------------------------------------
/src/solidlsp/ls_exceptions.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | This module contains the exceptions raised by the framework.
 3 | """
 4 | 
 5 | from solidlsp.ls_config import Language
 6 | 
 7 | 
 8 | class SolidLSPException(Exception):
 9 |     def __init__(self, message: str, cause: Exception | None = None) -> None:
10 |         """
11 |         Initializes the exception with the given message.
12 | 
13 |         :param message: the message describing the exception
14 |         :param cause: the original exception that caused this exception, if any.
15 |             For exceptions raised during request handling, this is typically
16 |                 * an LSPError for errors returned by the LSP server
17 |                 * LanguageServerTerminatedException for errors due to the language server having terminated.
18 |         """
19 |         self.cause = cause
20 |         super().__init__(message)
21 | 
22 |     def is_language_server_terminated(self) -> bool:
23 |         """
24 |         :return: True if the exception is caused by the language server having terminated as indicated
25 |             by the causing exception being an instance of LanguageServerTerminatedException.
26 |         """
27 |         from .ls_handler import LanguageServerTerminatedException
28 | 
29 |         return isinstance(self.cause, LanguageServerTerminatedException)
30 | 
31 |     def get_affected_language(self) -> Language | None:
32 |         """
33 |         :return: the affected language for the case where the exception is caused by the language server having terminated
34 |         """
35 |         from .ls_handler import LanguageServerTerminatedException
36 | 
37 |         if isinstance(self.cause, LanguageServerTerminatedException):
38 |             return self.cause.language
39 |         return None
40 | 
41 |     def __str__(self) -> str:
42 |         """
43 |         Returns a string representation of the exception.
44 |         """
45 |         s = super().__str__()
46 |         if self.cause:
47 |             if "\n" in s:
48 |                 s += "\n"
49 |             else:
50 |                 s += " "
51 |             s += f"(caused by {self.cause})"
52 |         return s
53 | 
```

--------------------------------------------------------------------------------
/docs/01-about/050_acknowledgements.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Acknowledgements
 2 | 
 3 | ## Sponsors
 4 | 
 5 | We are very grateful to our [sponsors](https://github.com/sponsors/oraios), who help us drive Serena's development. 
 6 | The core team (the founders of [Oraios AI](https://oraios-ai.de/)) put in a lot of work in order to turn Serena into a useful open source project.
 7 | So far, there is no business model behind this project, and sponsors are our only source of income from it.
 8 | 
 9 | Sponsors help us dedicate more time to the project, managing contributions, and working on larger features (like better tooling based on more advanced
10 | LSP features, VSCode integration, debugging via the DAP, and several others).
11 | If you find this project useful to your work, or would like to accelerate the development of Serena, consider becoming a sponsor.
12 | 
13 | We are proud to announce that the Visual Studio Code team, together with Microsoft’s Open Source Programs Office and GitHub Open Source
14 | have decided to sponsor Serena with a one-time contribution!
15 | 
16 | ## Community Contributions
17 | 
18 | A significant part of Serena, especially support for various languages, was contributed by the open source community.
19 | We are very grateful for the many contributors who made this possible and who played an important role in making Serena
20 | what it is today.
21 | 
22 | ## Technologies
23 | 
24 | We built Serena on top of multiple existing open-source technologies, the most important ones being:
25 | 
26 | 1. [multilspy](https://github.com/microsoft/multilspy).
27 |    A library which wraps language server implementations and adapts them for interaction via Python
28 |    and which provided the basis for our library Solid-LSP (src/solidlsp).
29 |    Solid-LSP provides pure synchronous LSP calls and extends the original library with the symbolic logic
30 |    that Serena required.
31 | 2. [Python MCP SDK](https://github.com/modelcontextprotocol/python-sdk)
32 | 3. All the language servers that we use through Solid-LSP.
33 | 
34 | Without these projects, Serena would not have been possible (or would have been significantly more difficult to build).
35 | 
```

--------------------------------------------------------------------------------
/src/solidlsp/lsp_protocol_handler/lsp_constants.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | This module contains constants used in the LSP protocol.
 3 | """
 4 | 
 5 | 
 6 | class LSPConstants:
 7 |     """
 8 |     This class contains constants used in the LSP protocol.
 9 |     """
10 | 
11 |     # the key for uri used to represent paths
12 |     URI = "uri"
13 | 
14 |     # the key for range, which is a from and to position within a text document
15 |     RANGE = "range"
16 | 
17 |     # A key used in LocationLink type, used as the span of the origin link
18 |     ORIGIN_SELECTION_RANGE = "originSelectionRange"
19 | 
20 |     # A key used in LocationLink type, used as the target uri of the link
21 |     TARGET_URI = "targetUri"
22 | 
23 |     # A key used in LocationLink type, used as the target range of the link
24 |     TARGET_RANGE = "targetRange"
25 | 
26 |     # A key used in LocationLink type, used as the target selection range of the link
27 |     TARGET_SELECTION_RANGE = "targetSelectionRange"
28 | 
29 |     # key for the textDocument field in the request
30 |     TEXT_DOCUMENT = "textDocument"
31 | 
32 |     # key used to represent the language a document is in - "java", "csharp", etc.
33 |     LANGUAGE_ID = "languageId"
34 | 
35 |     # key used to represent the version of a document (a shared value between the client and server)
36 |     VERSION = "version"
37 | 
38 |     # key used to represent the text of a document being sent from the client to the server on open
39 |     TEXT = "text"
40 | 
41 |     # key used to represent a position (line and colnum) within a text document
42 |     POSITION = "position"
43 | 
44 |     # key used to represent the line number of a position
45 |     LINE = "line"
46 | 
47 |     # key used to represent the column number of a position
48 |     CHARACTER = "character"
49 | 
50 |     # key used to represent the changes made to a document
51 |     CONTENT_CHANGES = "contentChanges"
52 | 
53 |     # key used to represent name of symbols
54 |     NAME = "name"
55 | 
56 |     # key used to represent the kind of symbols
57 |     KIND = "kind"
58 | 
59 |     # key used to represent children in document symbols
60 |     CHILDREN = "children"
61 | 
62 |     # key used to represent the location in symbols
63 |     LOCATION = "location"
64 | 
65 |     # Severity level of the diagnostic
66 |     SEVERITY = "severity"
67 | 
68 |     # The message of the diagnostic
69 |     MESSAGE = "message"
70 | 
```

--------------------------------------------------------------------------------
/src/serena/tools/cmd_tools.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Tools supporting the execution of (external) commands
 3 | """
 4 | 
 5 | import os.path
 6 | 
 7 | from serena.tools import Tool, ToolMarkerCanEdit
 8 | from serena.util.shell import execute_shell_command
 9 | 
10 | 
11 | class ExecuteShellCommandTool(Tool, ToolMarkerCanEdit):
12 |     """
13 |     Executes a shell command.
14 |     """
15 | 
16 |     def apply(
17 |         self,
18 |         command: str,
19 |         cwd: str | None = None,
20 |         capture_stderr: bool = True,
21 |         max_answer_chars: int = -1,
22 |     ) -> str:
23 |         """
24 |         Execute a shell command and return its output. If there is a memory about suggested commands, read that first.
25 |         Never execute unsafe shell commands!
26 |         IMPORTANT: Do not use this tool to start
27 |           * long-running processes (e.g. servers) that are not intended to terminate quickly,
28 |           * processes that require user interaction.
29 | 
30 |         :param command: the shell command to execute
31 |         :param cwd: the working directory to execute the command in. If None, the project root will be used.
32 |         :param capture_stderr: whether to capture and return stderr output
33 |         :param max_answer_chars: if the output is longer than this number of characters,
34 |             no content will be returned. -1 means using the default value, don't adjust unless there is no other way to get the content
35 |             required for the task.
36 |         :return: a JSON object containing the command's stdout and optionally stderr output
37 |         """
38 |         if cwd is None:
39 |             _cwd = self.get_project_root()
40 |         else:
41 |             if os.path.isabs(cwd):
42 |                 _cwd = cwd
43 |             else:
44 |                 _cwd = os.path.join(self.get_project_root(), cwd)
45 |                 if not os.path.isdir(_cwd):
46 |                     raise FileNotFoundError(
47 |                         f"Specified a relative working directory ({cwd}), but the resulting path is not a directory: {_cwd}"
48 |                     )
49 | 
50 |         result = execute_shell_command(command, cwd=_cwd, capture_stderr=capture_stderr)
51 |         result = result.json()
52 |         return self._limit_length(result, max_answer_chars)
53 | 
```

--------------------------------------------------------------------------------
/src/serena/resources/config/contexts/chatgpt.yml:
--------------------------------------------------------------------------------

```yaml
 1 | description: A configuration specific for ChatGPT, which has a limit of 30 tools and requires short descriptions.
 2 | prompt: |
 3 |   You are running in desktop app context where the tools give you access to the code base as well as some
 4 |   access to the file system, if configured. You interact with the user through a chat interface that is separated
 5 |   from the code base. As a consequence, if you are in interactive mode, your communication with the user should
 6 |   involve high-level thinking and planning as well as some summarization of any code edits that you make.
 7 |   For viewing the code edits the user will view them in a separate code editor window, and the back-and-forth
 8 |   between the chat and the code editor should be minimized as well as facilitated by you.
 9 |   If complex changes have been made, advise the user on how to review them in the code editor.
10 |   If complex relationships that the user asked for should be visualized or explained, consider creating
11 |   a diagram in addition to your text-based communication. Note that in the chat interface you have various rendering
12 |   options for text, html, and mermaid diagrams, as has been explained to you in your initial instructions.
13 | excluded_tools: []
14 | included_optional_tools:
15 |   - switch_modes
16 | 
17 | tool_description_overrides:
18 |   find_symbol: |
19 |     Retrieves symbols matching `name_path_pattern` in a file.
20 |     Use `depth > 0` to include children. `name_path_pattern` can be: "foo": any symbol named "foo"; "foo/bar": "bar" within "foo"; "/foo/bar": only top-level "foo/bar"
21 |   replace_content: |
22 |     Replaces content in files. Preferred for smaller edits where symbol-level tools aren't appropriate.
23 |     Use mode "regex" with wildcards (.*?) to match large sections efficiently: "beginning.*?end" instead of specifying exact content.
24 |     Essential for multi-line replacements.
25 |   search_for_pattern: |
26 |     Flexible pattern search across codebase. Prefer symbolic operations when possible.
27 |     Uses DOTALL matching. Use non-greedy quantifiers (.*?) to avoid over-matching.
28 |     Supports file filtering via globs and code-only restriction.
```

--------------------------------------------------------------------------------
/src/serena/util/thread.py:
--------------------------------------------------------------------------------

```python
 1 | import threading
 2 | from collections.abc import Callable
 3 | from enum import Enum
 4 | from typing import Generic, TypeVar
 5 | 
 6 | from sensai.util.string import ToStringMixin
 7 | 
 8 | 
 9 | class TimeoutException(Exception):
10 |     def __init__(self, message: str, timeout: float) -> None:
11 |         super().__init__(message)
12 |         self.timeout = timeout
13 | 
14 | 
15 | T = TypeVar("T")
16 | 
17 | 
18 | class ExecutionResult(Generic[T], ToStringMixin):
19 | 
20 |     class Status(Enum):
21 |         SUCCESS = "success"
22 |         TIMEOUT = "timeout"
23 |         EXCEPTION = "error"
24 | 
25 |     def __init__(self) -> None:
26 |         self.result_value: T | None = None
27 |         self.status: ExecutionResult.Status | None = None
28 |         self.exception: Exception | None = None
29 | 
30 |     def set_result_value(self, value: T) -> None:
31 |         self.result_value = value
32 |         self.status = ExecutionResult.Status.SUCCESS
33 | 
34 |     def set_timed_out(self, exception: TimeoutException) -> None:
35 |         self.exception = exception
36 |         self.status = ExecutionResult.Status.TIMEOUT
37 | 
38 |     def set_exception(self, exception: Exception) -> None:
39 |         self.exception = exception
40 |         self.status = ExecutionResult.Status.EXCEPTION
41 | 
42 | 
43 | def execute_with_timeout(func: Callable[[], T], timeout: float, function_name: str) -> ExecutionResult[T]:
44 |     """
45 |     Executes the given function with a timeout
46 | 
47 |     :param func: the function to execute
48 |     :param timeout: the timeout in seconds
49 |     :param function_name: the name of the function (for error messages)
50 |     :returns: the execution result
51 |     """
52 |     execution_result: ExecutionResult[T] = ExecutionResult()
53 | 
54 |     def target() -> None:
55 |         try:
56 |             value = func()
57 |             execution_result.set_result_value(value)
58 |         except Exception as e:
59 |             execution_result.set_exception(e)
60 | 
61 |     thread = threading.Thread(target=target, daemon=True)
62 |     thread.start()
63 |     thread.join(timeout=timeout)
64 | 
65 |     if thread.is_alive():
66 |         timeout_exception = TimeoutException(f"Execution of '{function_name}' timed out after {timeout} seconds.", timeout)
67 |         execution_result.set_timed_out(timeout_exception)
68 | 
69 |     return execution_result
70 | 
```

--------------------------------------------------------------------------------
/docs/02-usage/999_additional-usage.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Additional Usage Pointers
 2 | 
 3 | ## Prompting Strategies
 4 | 
 5 | We found that it is often a good idea to spend some time conceptualizing and planning a task
 6 | before actually implementing it, especially for non-trivial task. This helps both in achieving
 7 | better results and in increasing the feeling of control and staying in the loop. You can
 8 | make a detailed plan in one session, where Serena may read a lot of your code to build up the context,
 9 | and then continue with the implementation in another (potentially after creating suitable memories).
10 | 
11 | ## Running Out of Context
12 | 
13 | For long and complicated tasks, or tasks where Serena has read a lot of content, you
14 | may come close to the limits of context tokens. In that case, it is often a good idea to continue
15 | in a new conversation. Serena has a dedicated tool to create a summary of the current state
16 | of the progress and all relevant info for continuing it. You can request to create this summary and
17 | write it to a memory. Then, in a new conversation, you can just ask Serena to read the memory and
18 | continue with the task. In our experience, this worked really well. On the up-side, since in a
19 | single session there is no summarization involved, Serena does not usually get lost (unlike some
20 | other agents that summarize under the hood), and it is also instructed to occasionally check whether
21 | it's on the right track.
22 | 
23 | Serena instructs the LLM to be economical in general, so the problem of running out of context
24 | should not occur too often, unless the task is very large or complicated.
25 | 
26 | ## Serena and Git Worktrees
27 | 
28 | [git-worktree](https://git-scm.com/docs/git-worktree) can be an excellent way to parallelize your work. More on this in [Anthropic: Run parallel Claude Code sessions with Git worktrees](https://docs.claude.com/en/docs/claude-code/common-workflows#run-parallel-claude-code-sessions-with-git-worktrees).
29 | 
30 | When it comes to serena AND git-worktree AND larger projects (that take longer to index), 
31 | the recommended way is to COPY your `$ORIG_PROJECT/.serena/cache` to `$GIT_WORKTREE/.serena/cache`. 
32 | Perform [pre-indexing of your project](indexing) to avoid having to re-index per each worktree you create. 
33 | 
```

--------------------------------------------------------------------------------
/test/resources/repos/bash/test_repo/config.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | 
 3 | # Configuration script for project setup
 4 | 
 5 | # Environment variables
 6 | export PROJECT_NAME="bash-test-project"
 7 | export PROJECT_VERSION="1.0.0"
 8 | export LOG_LEVEL="INFO"
 9 | export CONFIG_DIR="./config"
10 | 
11 | # Default settings
12 | DEFAULT_TIMEOUT=30
13 | DEFAULT_RETRIES=3
14 | DEFAULT_PORT=8080
15 | 
16 | # Configuration arrays
17 | declare -A ENVIRONMENTS=(
18 |     ["dev"]="development"
19 |     ["prod"]="production"
20 |     ["test"]="testing"
21 | )
22 | 
23 | declare -A DATABASE_CONFIGS=(
24 |     ["host"]="localhost"
25 |     ["port"]="5432"
26 |     ["name"]="myapp_db"
27 |     ["user"]="dbuser"
28 | )
29 | 
30 | # Function to load configuration
31 | load_config() {
32 |     local env="${1:-dev}"
33 |     local config_file="${CONFIG_DIR}/${env}.conf"
34 |     
35 |     if [[ -f "$config_file" ]]; then
36 |         echo "Loading configuration from $config_file"
37 |         source "$config_file"
38 |     else
39 |         echo "Warning: Configuration file $config_file not found, using defaults"
40 |     fi
41 | }
42 | 
43 | # Function to validate configuration
44 | validate_config() {
45 |     local errors=0
46 |     
47 |     if [[ -z "$PROJECT_NAME" ]]; then
48 |         echo "Error: PROJECT_NAME is not set" >&2
49 |         ((errors++))
50 |     fi
51 |     
52 |     if [[ -z "$PROJECT_VERSION" ]]; then
53 |         echo "Error: PROJECT_VERSION is not set" >&2
54 |         ((errors++))
55 |     fi
56 |     
57 |     if [[ $DEFAULT_PORT -lt 1024 || $DEFAULT_PORT -gt 65535 ]]; then
58 |         echo "Error: Invalid port number $DEFAULT_PORT" >&2
59 |         ((errors++))
60 |     fi
61 |     
62 |     return $errors
63 | }
64 | 
65 | # Function to print configuration
66 | print_config() {
67 |     echo "=== Current Configuration ==="
68 |     echo "Project Name: $PROJECT_NAME"
69 |     echo "Version: $PROJECT_VERSION"
70 |     echo "Log Level: $LOG_LEVEL"
71 |     echo "Default Port: $DEFAULT_PORT"
72 |     echo "Default Timeout: $DEFAULT_TIMEOUT"
73 |     echo "Default Retries: $DEFAULT_RETRIES"
74 |     
75 |     echo "\n=== Environments ==="
76 |     for env in "${!ENVIRONMENTS[@]}"; do
77 |         echo "  $env: ${ENVIRONMENTS[$env]}"
78 |     done
79 |     
80 |     echo "\n=== Database Configuration ==="
81 |     for key in "${!DATABASE_CONFIGS[@]}"; do
82 |         echo "  $key: ${DATABASE_CONFIGS[$key]}"
83 |     done
84 | }
85 | 
86 | # Initialize configuration if this script is run directly
87 | if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
88 |     load_config "$1"
89 |     validate_config
90 |     print_config
91 | fi
92 | 
```

--------------------------------------------------------------------------------
/src/serena/util/exception.py:
--------------------------------------------------------------------------------

```python
 1 | import os
 2 | import sys
 3 | 
 4 | from serena.agent import log
 5 | 
 6 | 
 7 | def is_headless_environment() -> bool:
 8 |     """
 9 |     Detect if we're running in a headless environment where GUI operations would fail.
10 | 
11 |     Returns True if:
12 |     - No DISPLAY variable on Linux/Unix
13 |     - Running in SSH session
14 |     - Running in WSL without X server
15 |     - Running in Docker container
16 |     """
17 |     # Check if we're on Windows - GUI usually works there
18 |     if sys.platform == "win32":
19 |         return False
20 | 
21 |     # Check for DISPLAY variable (required for X11)
22 |     if not os.environ.get("DISPLAY"):  # type: ignore
23 |         return True
24 | 
25 |     # Check for SSH session
26 |     if os.environ.get("SSH_CONNECTION") or os.environ.get("SSH_CLIENT"):
27 |         return True
28 | 
29 |     # Check for common CI/container environments
30 |     if os.environ.get("CI") or os.environ.get("CONTAINER") or os.path.exists("/.dockerenv"):
31 |         return True
32 | 
33 |     # Check for WSL (only on Unix-like systems where os.uname exists)
34 |     if hasattr(os, "uname"):
35 |         if "microsoft" in os.uname().release.lower():
36 |             # In WSL, even with DISPLAY set, X server might not be running
37 |             # This is a simplified check - could be improved
38 |             return True
39 | 
40 |     return False
41 | 
42 | 
43 | def show_fatal_exception_safe(e: Exception) -> None:
44 |     """
45 |     Shows the given exception in the GUI log viewer on the main thread and ensures that the exception is logged or at
46 |     least printed to stderr.
47 |     """
48 |     # Log the error and print it to stderr
49 |     log.error(f"Fatal exception: {e}", exc_info=e)
50 |     print(f"Fatal exception: {e}", file=sys.stderr)
51 | 
52 |     # Don't attempt GUI in headless environments
53 |     if is_headless_environment():
54 |         log.debug("Skipping GUI error display in headless environment")
55 |         return
56 | 
57 |     # attempt to show the error in the GUI
58 |     try:
59 |         # NOTE: The import can fail on macOS if Tk is not available (depends on Python interpreter installation, which uv
60 |         #   used as a base); while tkinter as such is always available, its dependencies can be unavailable on macOS.
61 |         from serena.gui_log_viewer import show_fatal_exception
62 | 
63 |         show_fatal_exception(e)
64 |     except Exception as gui_error:
65 |         log.debug(f"Failed to show GUI error dialog: {gui_error}")
66 | 
```

--------------------------------------------------------------------------------
/test/resources/repos/python/test_repo/test_repo/services.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Services module demonstrating function usage and dependencies.
 3 | """
 4 | 
 5 | from typing import Any
 6 | 
 7 | from .models import Item, User
 8 | 
 9 | 
10 | class UserService:
11 |     """Service for user-related operations"""
12 | 
13 |     def __init__(self, user_db: dict[str, User] | None = None):
14 |         self.users = user_db or {}
15 | 
16 |     def create_user(self, id: str, name: str, email: str) -> User:
17 |         """Create a new user and store it"""
18 |         if id in self.users:
19 |             raise ValueError(f"User with ID {id} already exists")
20 | 
21 |         user = User(id=id, name=name, email=email)
22 |         self.users[id] = user
23 |         return user
24 | 
25 |     def get_user(self, id: str) -> User | None:
26 |         """Get a user by ID"""
27 |         return self.users.get(id)
28 | 
29 |     def list_users(self) -> list[User]:
30 |         """Get a list of all users"""
31 |         return list(self.users.values())
32 | 
33 |     def delete_user(self, id: str) -> bool:
34 |         """Delete a user by ID"""
35 |         if id in self.users:
36 |             del self.users[id]
37 |             return True
38 |         return False
39 | 
40 | 
41 | class ItemService:
42 |     """Service for item-related operations"""
43 | 
44 |     def __init__(self, item_db: dict[str, Item] | None = None):
45 |         self.items = item_db or {}
46 | 
47 |     def create_item(self, id: str, name: str, price: float, category: str) -> Item:
48 |         """Create a new item and store it"""
49 |         if id in self.items:
50 |             raise ValueError(f"Item with ID {id} already exists")
51 | 
52 |         item = Item(id=id, name=name, price=price, category=category)
53 |         self.items[id] = item
54 |         return item
55 | 
56 |     def get_item(self, id: str) -> Item | None:
57 |         """Get an item by ID"""
58 |         return self.items.get(id)
59 | 
60 |     def list_items(self, category: str | None = None) -> list[Item]:
61 |         """List all items, optionally filtered by category"""
62 |         if category:
63 |             return [item for item in self.items.values() if item.category == category]
64 |         return list(self.items.values())
65 | 
66 | 
67 | # Factory function for services
68 | def create_service_container() -> dict[str, Any]:
69 |     """Create a container with all services"""
70 |     container = {"user_service": UserService(), "item_service": ItemService()}
71 |     return container
72 | 
73 | 
74 | user_var_str = "user_var"
75 | 
76 | 
77 | user_service = UserService()
78 | user_service.create_user("1", "Alice", "[email protected]")
79 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/ruby/test_ruby_basic.py:
--------------------------------------------------------------------------------

```python
 1 | import os
 2 | from pathlib import Path
 3 | 
 4 | import pytest
 5 | 
 6 | from solidlsp import SolidLanguageServer
 7 | from solidlsp.ls_config import Language
 8 | from solidlsp.ls_utils import SymbolUtils
 9 | 
10 | 
11 | @pytest.mark.ruby
12 | class TestRubyLanguageServer:
13 |     @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
14 |     def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
15 |         symbols = language_server.request_full_symbol_tree()
16 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "DemoClass"), "DemoClass not found in symbol tree"
17 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "helper_function"), "helper_function not found in symbol tree"
18 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "print_value"), "print_value not found in symbol tree"
19 | 
20 |     @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
21 |     def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
22 |         file_path = os.path.join("main.rb")
23 |         symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
24 |         helper_symbol = None
25 |         for sym in symbols[0]:
26 |             if sym.get("name") == "helper_function":
27 |                 helper_symbol = sym
28 |                 break
29 |         print(helper_symbol)
30 |         assert helper_symbol is not None, "Could not find 'helper_function' symbol in main.rb"
31 | 
32 |     @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
33 |     @pytest.mark.parametrize("repo_path", [Language.RUBY], indirect=True)
34 |     def test_find_definition_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
35 |         # Test finding Calculator.add method definition from line 17: Calculator.new.add(demo.value, 10)
36 |         definition_location_list = language_server.request_definition(
37 |             str(repo_path / "main.rb"), 16, 17
38 |         )  # add method at line 17 (0-indexed 16), position 17
39 | 
40 |         assert len(definition_location_list) == 1
41 |         definition_location = definition_location_list[0]
42 |         print(f"Found definition: {definition_location}")
43 |         assert definition_location["uri"].endswith("lib.rb")
44 |         assert definition_location["range"]["start"]["line"] == 1  # add method on line 2 (0-indexed 1)
45 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/julia/test_julia_basic.py:
--------------------------------------------------------------------------------

```python
 1 | import pytest
 2 | 
 3 | from solidlsp.ls import SolidLanguageServer
 4 | from solidlsp.ls_config import Language
 5 | 
 6 | 
 7 | @pytest.mark.julia
 8 | class TestJuliaLanguageServer:
 9 |     @pytest.mark.parametrize("language_server", [Language.JULIA], indirect=True)
10 |     def test_julia_symbols(self, language_server: SolidLanguageServer):
11 |         """
12 |         Test if we can find the top-level symbols in the main.jl file.
13 |         """
14 |         all_symbols, _ = language_server.request_document_symbols("main.jl").get_all_symbols_and_roots()
15 |         symbol_names = {s["name"] for s in all_symbols}
16 |         assert "calculate_sum" in symbol_names
17 |         assert "main" in symbol_names
18 | 
19 |     @pytest.mark.parametrize("language_server", [Language.JULIA], indirect=True)
20 |     def test_julia_within_file_references(self, language_server: SolidLanguageServer):
21 |         """
22 |         Test finding references to a function within the same file.
23 |         """
24 |         # Find references to 'calculate_sum' - the function name starts at line 2, column 9
25 |         # LSP uses 0-based indexing
26 |         references = language_server.request_references("main.jl", line=2, column=9)
27 | 
28 |         # Should find at least the definition and the call site
29 |         assert len(references) >= 1, f"Expected at least 1 reference, got {len(references)}"
30 | 
31 |         # Verify at least one reference is in main.jl
32 |         reference_paths = [ref["relativePath"] for ref in references]
33 |         assert "main.jl" in reference_paths
34 | 
35 |     @pytest.mark.parametrize("language_server", [Language.JULIA], indirect=True)
36 |     def test_julia_cross_file_references(self, language_server: SolidLanguageServer):
37 |         """
38 |         Test finding references to a function defined in another file.
39 |         """
40 |         # The 'say_hello' function name starts at line 1, column 13 in lib/helper.jl
41 |         # LSP uses 0-based indexing
42 |         references = language_server.request_references("lib/helper.jl", line=1, column=13)
43 | 
44 |         # Should find at least the call site in main.jl
45 |         assert len(references) >= 1, f"Expected at least 1 reference, got {len(references)}"
46 | 
47 |         # Verify at least one reference points to the usage
48 |         reference_paths = [ref["relativePath"] for ref in references]
49 |         # The reference might be in either file (definition or usage)
50 |         assert "main.jl" in reference_paths or "lib/helper.jl" in reference_paths
51 | 
```

--------------------------------------------------------------------------------
/test/resources/repos/python/test_repo/test_repo/overloaded.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Module demonstrating function and method overloading with typing.overload
 3 | """
 4 | 
 5 | from typing import Any, overload
 6 | 
 7 | 
 8 | # Example of function overloading
 9 | @overload
10 | def process_data(data: str) -> dict[str, str]: ...
11 | 
12 | 
13 | @overload
14 | def process_data(data: int) -> dict[str, int]: ...
15 | 
16 | 
17 | @overload
18 | def process_data(data: list[str | int]) -> dict[str, list[str | int]]: ...
19 | 
20 | 
21 | def process_data(data: str | int | list[str | int]) -> dict[str, Any]:
22 |     """
23 |     Process data based on its type.
24 | 
25 |     - If string: returns a dict with 'value': <string>
26 |     - If int: returns a dict with 'value': <int>
27 |     - If list: returns a dict with 'value': <list>
28 |     """
29 |     return {"value": data}
30 | 
31 | 
32 | # Class with overloaded methods
33 | class DataProcessor:
34 |     """
35 |     A class demonstrating method overloading.
36 |     """
37 | 
38 |     @overload
39 |     def transform(self, input_value: str) -> str: ...
40 | 
41 |     @overload
42 |     def transform(self, input_value: int) -> int: ...
43 | 
44 |     @overload
45 |     def transform(self, input_value: list[Any]) -> list[Any]: ...
46 | 
47 |     def transform(self, input_value: str | int | list[Any]) -> str | int | list[Any]:
48 |         """
49 |         Transform input based on its type.
50 | 
51 |         - If string: returns the string in uppercase
52 |         - If int: returns the int multiplied by 2
53 |         - If list: returns the list sorted
54 |         """
55 |         if isinstance(input_value, str):
56 |             return input_value.upper()
57 |         elif isinstance(input_value, int):
58 |             return input_value * 2
59 |         elif isinstance(input_value, list):
60 |             try:
61 |                 return sorted(input_value)
62 |             except TypeError:
63 |                 return input_value
64 |         return input_value
65 | 
66 |     @overload
67 |     def fetch(self, id: int) -> dict[str, Any]: ...
68 | 
69 |     @overload
70 |     def fetch(self, id: str, cache: bool = False) -> dict[str, Any] | None: ...
71 | 
72 |     def fetch(self, id: int | str, cache: bool = False) -> dict[str, Any] | None:
73 |         """
74 |         Fetch data for a given ID.
75 | 
76 |         Args:
77 |             id: The ID to fetch, either numeric or string
78 |             cache: Whether to use cache for string IDs
79 | 
80 |         Returns:
81 |             Data dictionary or None if not found
82 | 
83 |         """
84 |         # Implementation would actually fetch data
85 |         if isinstance(id, int):
86 |             return {"id": id, "type": "numeric"}
87 |         else:
88 |             return {"id": id, "type": "string", "cached": cache}
89 | 
```

--------------------------------------------------------------------------------
/docs/01-about/040_comparison-to-other-agents.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Comparison with Other Coding Agents
 2 | 
 3 | To our knowledge, Serena is the first fully-featured coding agent where the
 4 | entire functionality is made available through an MCP server, 
 5 | thus not requiring additional API keys or subscriptions if access to an LLM
 6 | is already available through an MCP-compatible client.
 7 | 
 8 | ## Subscription-Based Coding Agents
 9 | 
10 | Many prominent subscription-based coding agents are parts of IDEs like
11 | Windsurf, Cursor and VSCode.
12 | Serena's functionality is similar to Cursor's Agent, Windsurf's Cascade or
13 | VSCode's agent mode.
14 | 
15 | Serena has the advantage of not requiring a subscription.
16 | 
17 | More technical differences are:
18 | 
19 | * Serena navigates and edits code using a language server, so it has a symbolic
20 |   understanding of the code.
21 |   IDE-based tools often use a text search-based or purely text file-based approach, which is often
22 |   less powerful, especially for large codebases.
23 | * Serena is not bound to a specific interface (IDE or CLI).
24 |   Serena's MCP server can be used with any MCP client (including some IDEs).
25 | * Serena is not bound to a specific large language model or API.
26 | * Serena is open-source and has a small codebase, so it can be easily extended
27 |   and modified.
28 | 
29 | ## API-Based Coding Agents
30 | 
31 | An alternative to subscription-based agents are API-based agents like Claude
32 | Code, Cline, Aider, Roo Code and others, where the usage costs map directly
33 | to the API costs of the underlying LLM.
34 | Some of them (like Cline) can even be included in IDEs as an extension.
35 | They are often very powerful and their main downside are the (potentially very
36 | high) API costs.
37 | Serena itself can be used as an API-based agent (see the [section on Agno](../03-special-guides/custom_agent.md)).
38 | 
39 | The main difference between Serena and other API-based agents is that Serena can
40 | also be used as an MCP server, thus not requiring
41 | an API key and bypassing the API costs.
42 | 
43 | ## Other MCP-Based Coding Agents
44 | 
45 | There are other MCP servers designed for coding, like [DesktopCommander](https://github.com/wonderwhy-er/DesktopCommanderMCP) and
46 | [codemcp](https://github.com/ezyang/codemcp).
47 | However, to the best of our knowledge, none of them provide semantic code
48 | retrieval and editing tools; they rely purely on text-based analysis.
49 | It is the integration of language servers and the MCP that makes Serena unique
50 | and so powerful for challenging coding tasks, especially in the context of
51 | larger codebases.
```

--------------------------------------------------------------------------------
/test/resources/repos/typescript/test_repo/ws_manager.js:
--------------------------------------------------------------------------------

```javascript
 1 | /**
 2 |  * Dummy WebSocket manager class for testing ambiguous regex matching.
 3 |  */
 4 | class WebSocketManager {
 5 |     constructor() {
 6 |         console.log("WebSocketManager initializing\nStatus OK");
 7 |         this.ws = null;
 8 |         this.statusElement = document.getElementById("status");
 9 |     }
10 | 
11 |     /**
12 |      * Connects to the WebSocket server.
13 |      */
14 |     connectToServer() {
15 |         if (this.ws?.readyState === WebSocket.OPEN) {
16 |             this.updateConnectionStatus("Already connected", true);
17 |             return;
18 |         }
19 | 
20 |         try {
21 |             this.ws = new WebSocket("ws://localhost:4402");
22 |             this.updateConnectionStatus("Connecting...", false);
23 | 
24 |             this.ws.onopen = () => {
25 |                 console.log("Connected to server");
26 |                 this.updateConnectionStatus("Connected", true);
27 |             };
28 | 
29 |             this.ws.onmessage = (event) => {
30 |                 console.log("Received message:", event.data);
31 |                 try {
32 |                     const data = JSON.parse(event.data);
33 |                     this.handleMessage(data);
34 |                 } catch (error) {
35 |                     console.error("Failed to parse message:", error);
36 |                     this.updateConnectionStatus("Parse error", false);
37 |                 }
38 |             };
39 | 
40 |             this.ws.onclose = (event) => {
41 |                 console.log("Connection closed");
42 |                 const message = event.reason || undefined;
43 |                 this.updateConnectionStatus("Disconnected", false, message);
44 |                 this.ws = null;
45 |             };
46 | 
47 |             this.ws.onerror = (error) => {
48 |                 console.error("WebSocket error:", error);
49 |                 this.updateConnectionStatus("Connection error", false);
50 |             };
51 |         } catch (error) {
52 |             console.error("Failed to connect to server:", error);
53 |             this.updateConnectionStatus("Connection failed", false);
54 |         }
55 |     }
56 | 
57 |     /**
58 |      * Updates the connection status display.
59 |      */
60 |     updateConnectionStatus(status, isConnected, message) {
61 |         if (this.statusElement) {
62 |             const text = message ? `${status}: ${message}` : status;
63 |             this.statusElement.textContent = text;
64 |             this.statusElement.style.color = isConnected ? "green" : "red";
65 |         }
66 |     }
67 | 
68 |     /**
69 |      * Handles incoming messages.
70 |      */
71 |     handleMessage(data) {
72 |         console.log("Handling:", data);
73 |     }
74 | }
```

--------------------------------------------------------------------------------
/test/solidlsp/scala/test_scala_language_server.py:
--------------------------------------------------------------------------------

```python
 1 | import os
 2 | 
 3 | import pytest
 4 | 
 5 | from solidlsp.language_servers.scala_language_server import ScalaLanguageServer
 6 | from solidlsp.ls_config import Language, LanguageServerConfig
 7 | from solidlsp.settings import SolidLSPSettings
 8 | 
 9 | pytest.skip("Scala must be compiled for these tests to run through, which is a huge hassle", allow_module_level=True)
10 | 
11 | MAIN_FILE_PATH = os.path.join("src", "main", "scala", "com", "example", "Main.scala")
12 | 
13 | pytestmark = pytest.mark.scala
14 | 
15 | 
16 | @pytest.fixture(scope="module")
17 | def scala_ls():
18 |     repo_root = os.path.abspath("test/resources/repos/scala")
19 |     config = LanguageServerConfig(code_language=Language.SCALA)
20 |     solidlsp_settings = SolidLSPSettings()
21 |     ls = ScalaLanguageServer(config, repo_root, solidlsp_settings)
22 | 
23 |     with ls.start_server():
24 |         yield ls
25 | 
26 | 
27 | def test_scala_document_symbols(scala_ls):
28 |     """Test document symbols for Main.scala"""
29 |     symbols, _ = scala_ls.request_document_symbols(MAIN_FILE_PATH).get_all_symbols_and_roots()
30 |     symbol_names = [s["name"] for s in symbols]
31 |     assert symbol_names[0] == "com.example"
32 |     assert symbol_names[1] == "Main"
33 |     assert symbol_names[2] == "main"
34 |     assert symbol_names[3] == "result"
35 |     assert symbol_names[4] == "sum"
36 |     assert symbol_names[5] == "add"
37 |     assert symbol_names[6] == "someMethod"
38 |     assert symbol_names[7] == "str"
39 |     assert symbol_names[8] == "Config"
40 |     assert symbol_names[9] == "field1"  # confirm https://github.com/oraios/serena/issues/688
41 | 
42 | 
43 | def test_scala_references_within_same_file(scala_ls):
44 |     """Test finding references within the same file."""
45 |     definitions = scala_ls.request_definition(MAIN_FILE_PATH, 12, 23)
46 |     first_def = definitions[0]
47 |     assert first_def["uri"].endswith("Main.scala")
48 |     assert first_def["range"]["start"]["line"] == 16
49 |     assert first_def["range"]["start"]["character"] == 6
50 |     assert first_def["range"]["end"]["line"] == 16
51 |     assert first_def["range"]["end"]["character"] == 9
52 | 
53 | 
54 | def test_scala_find_definition_and_references_across_files(scala_ls):
55 |     definitions = scala_ls.request_definition(MAIN_FILE_PATH, 8, 25)
56 |     assert len(definitions) == 1
57 | 
58 |     first_def = definitions[0]
59 |     assert first_def["uri"].endswith("Utils.scala")
60 |     assert first_def["range"]["start"]["line"] == 7
61 |     assert first_def["range"]["start"]["character"] == 6
62 |     assert first_def["range"]["end"]["line"] == 7
63 |     assert first_def["range"]["end"]["character"] == 14
64 | 
```

--------------------------------------------------------------------------------
/test/resources/repos/ruby/test_repo/examples/user_management.rb:
--------------------------------------------------------------------------------

```ruby
  1 | require '../services.rb'
  2 | require '../models.rb'
  3 | 
  4 | class UserStats
  5 |   attr_reader :user_count, :active_users, :last_updated
  6 | 
  7 |   def initialize
  8 |     @user_count = 0
  9 |     @active_users = 0
 10 |     @last_updated = Time.now
 11 |   end
 12 | 
 13 |   def update_stats(total, active)
 14 |     @user_count = total
 15 |     @active_users = active
 16 |     @last_updated = Time.now
 17 |   end
 18 | 
 19 |   def activity_ratio
 20 |     return 0.0 if @user_count == 0
 21 |     (@active_users.to_f / @user_count * 100).round(2)
 22 |   end
 23 | 
 24 |   def formatted_stats
 25 |     "Users: #{@user_count}, Active: #{@active_users} (#{activity_ratio}%)"
 26 |   end
 27 | end
 28 | 
 29 | class UserManager
 30 |   def initialize
 31 |     @service = Services::UserService.new
 32 |     @stats = UserStats.new
 33 |   end
 34 | 
 35 |   def create_user_with_tracking(id, name, email = nil)
 36 |     user = @service.create_user(id, name)
 37 |     user.email = email if email
 38 |     
 39 |     update_statistics
 40 |     notify_user_created(user)
 41 |     
 42 |     user
 43 |   end
 44 | 
 45 |   def get_user_details(id)
 46 |     user = @service.get_user(id)
 47 |     return nil unless user
 48 |     
 49 |     {
 50 |       user_info: user.full_info,
 51 |       created_at: Time.now,
 52 |       stats: @stats.formatted_stats
 53 |     }
 54 |   end
 55 | 
 56 |   def bulk_create_users(user_data_list)
 57 |     created_users = []
 58 |     
 59 |     user_data_list.each do |data|
 60 |       user = create_user_with_tracking(data[:id], data[:name], data[:email])
 61 |       created_users << user
 62 |     end
 63 |     
 64 |     created_users
 65 |   end
 66 | 
 67 |   private
 68 | 
 69 |   def update_statistics
 70 |     total_users = @service.users.length
 71 |     # For demo purposes, assume all users are active
 72 |     @stats.update_stats(total_users, total_users)
 73 |   end
 74 | 
 75 |   def notify_user_created(user)
 76 |     puts "User created: #{user.name} (ID: #{user.id})"
 77 |   end
 78 | end
 79 | 
 80 | def process_user_data(raw_data)
 81 |   processed = raw_data.map do |entry|
 82 |     {
 83 |       id: entry["id"] || entry[:id],
 84 |       name: entry["name"] || entry[:name],
 85 |       email: entry["email"] || entry[:email]
 86 |     }
 87 |   end
 88 |   
 89 |   processed.reject { |entry| entry[:name].nil? || entry[:name].empty? }
 90 | end
 91 | 
 92 | def main
 93 |   # Example usage
 94 |   manager = UserManager.new
 95 |   
 96 |   sample_data = [
 97 |     { id: 1, name: "Alice Johnson", email: "[email protected]" },
 98 |     { id: 2, name: "Bob Smith", email: "[email protected]" },
 99 |     { id: 3, name: "Charlie Brown" }
100 |   ]
101 |   
102 |   users = manager.bulk_create_users(sample_data)
103 |   
104 |   users.each do |user|
105 |     details = manager.get_user_details(user.id)
106 |     puts details[:user_info]
107 |   end
108 |   
109 |   puts "\nFinal statistics:"
110 |   stats = UserStats.new
111 |   stats.update_stats(users.length, users.length)
112 |   puts stats.formatted_stats
113 | end
114 | 
115 | # Execute if this file is run directly
116 | main if __FILE__ == $0
```

--------------------------------------------------------------------------------
/test/resources/repos/powershell/test_repo/main.ps1:
--------------------------------------------------------------------------------

```
  1 | # Main script demonstrating various PowerShell features
  2 | 
  3 | # Import utility functions
  4 | . "$PSScriptRoot\utils.ps1"
  5 | 
  6 | # Global variables
  7 | $Script:ScriptName = "Main Script"
  8 | $Script:Counter = 0
  9 | 
 10 | <#
 11 | .SYNOPSIS
 12 |     Greets a user with various greeting styles.
 13 | .PARAMETER Username
 14 |     The name of the user to greet.
 15 | .PARAMETER GreetingType
 16 |     The type of greeting (formal, casual, or default).
 17 | #>
 18 | function Greet-User {
 19 |     [CmdletBinding()]
 20 |     param(
 21 |         [Parameter(Mandatory = $true)]
 22 |         [string]$Username,
 23 | 
 24 |         [Parameter(Mandatory = $false)]
 25 |         [ValidateSet("formal", "casual", "default")]
 26 |         [string]$GreetingType = "default"
 27 |     )
 28 | 
 29 |     switch ($GreetingType) {
 30 |         "formal" {
 31 |             Write-Output "Good day, $Username!"
 32 |         }
 33 |         "casual" {
 34 |             Write-Output "Hey $Username!"
 35 |         }
 36 |         default {
 37 |             Write-Output "Hello, $Username!"
 38 |         }
 39 |     }
 40 | }
 41 | 
 42 | <#
 43 | .SYNOPSIS
 44 |     Processes an array of items with the specified operation.
 45 | .PARAMETER Items
 46 |     The array of items to process.
 47 | .PARAMETER Operation
 48 |     The operation to perform (count, uppercase).
 49 | #>
 50 | function Process-Items {
 51 |     [CmdletBinding()]
 52 |     param(
 53 |         [Parameter(Mandatory = $true)]
 54 |         [string[]]$Items,
 55 | 
 56 |         [Parameter(Mandatory = $true)]
 57 |         [ValidateSet("count", "uppercase")]
 58 |         [string]$Operation
 59 |     )
 60 | 
 61 |     foreach ($item in $Items) {
 62 |         switch ($Operation) {
 63 |             "count" {
 64 |                 $Script:Counter++
 65 |                 Write-Output "Processing item $($Script:Counter): $item"
 66 |             }
 67 |             "uppercase" {
 68 |                 Write-Output $item.ToUpper()
 69 |             }
 70 |         }
 71 |     }
 72 | }
 73 | 
 74 | <#
 75 | .SYNOPSIS
 76 |     Main entry point for the script.
 77 | #>
 78 | function Main {
 79 |     [CmdletBinding()]
 80 |     param(
 81 |         [Parameter(Mandatory = $false)]
 82 |         [string]$User = "World",
 83 | 
 84 |         [Parameter(Mandatory = $false)]
 85 |         [string]$Greeting = "default"
 86 |     )
 87 | 
 88 |     Write-Output "Starting $Script:ScriptName"
 89 | 
 90 |     # Use the Greet-User function
 91 |     Greet-User -Username $User -GreetingType $Greeting
 92 | 
 93 |     # Process some items
 94 |     $items = @("item1", "item2", "item3")
 95 |     Write-Output "Processing items..."
 96 |     Process-Items -Items $items -Operation "count"
 97 | 
 98 |     # Use utility functions from utils.ps1
 99 |     $upperName = Convert-ToUpperCase -InputString $User
100 |     Write-Output "Uppercase name: $upperName"
101 | 
102 |     $trimmed = Remove-Whitespace -InputString "  Hello World  "
103 |     Write-Output "Trimmed: '$trimmed'"
104 | 
105 |     Write-Output "Script completed successfully"
106 | }
107 | 
108 | # Run main function
109 | Main @args
110 | 
```

--------------------------------------------------------------------------------
/test/resources/repos/vue/test_repo/src/composables/useTheme.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ref, computed, watch, inject, provide, type InjectionKey, type Ref } from 'vue'
  2 | 
  3 | /**
  4 |  * Theme configuration type
  5 |  */
  6 | export interface ThemeConfig {
  7 |   isDark: boolean
  8 |   primaryColor: string
  9 |   fontSize: number
 10 | }
 11 | 
 12 | /**
 13 |  * Injection key for theme - demonstrates provide/inject pattern
 14 |  */
 15 | export const ThemeKey: InjectionKey<Ref<ThemeConfig>> = Symbol('theme')
 16 | 
 17 | /**
 18 |  * Composable for theme management with watchers.
 19 |  * Demonstrates: watch, provide/inject, localStorage interaction
 20 |  */
 21 | export function useThemeProvider() {
 22 |   // Initialize theme from localStorage or defaults
 23 |   const loadThemeFromStorage = (): ThemeConfig => {
 24 |     const stored = localStorage.getItem('app-theme')
 25 |     if (stored) {
 26 |       try {
 27 |         return JSON.parse(stored)
 28 |       } catch {
 29 |         // Fall through to defaults
 30 |       }
 31 |     }
 32 |     return {
 33 |       isDark: false,
 34 |       primaryColor: '#667eea',
 35 |       fontSize: 16
 36 |     }
 37 |   }
 38 | 
 39 |   const theme = ref<ThemeConfig>(loadThemeFromStorage())
 40 | 
 41 |   // Computed properties
 42 |   const isDarkMode = computed(() => theme.value.isDark)
 43 |   const themeClass = computed(() => theme.value.isDark ? 'dark-theme' : 'light-theme')
 44 | 
 45 |   // Watch for theme changes and persist to localStorage
 46 |   watch(
 47 |     theme,
 48 |     (newTheme) => {
 49 |       localStorage.setItem('app-theme', JSON.stringify(newTheme))
 50 |       document.documentElement.className = newTheme.isDark ? 'dark' : 'light'
 51 |     },
 52 |     { deep: true }
 53 |   )
 54 | 
 55 |   // Methods
 56 |   const toggleDarkMode = (): void => {
 57 |     theme.value.isDark = !theme.value.isDark
 58 |   }
 59 | 
 60 |   const setPrimaryColor = (color: string): void => {
 61 |     theme.value.primaryColor = color
 62 |   }
 63 | 
 64 |   const setFontSize = (size: number): void => {
 65 |     if (size >= 12 && size <= 24) {
 66 |       theme.value.fontSize = size
 67 |     }
 68 |   }
 69 | 
 70 |   const resetTheme = (): void => {
 71 |     theme.value = {
 72 |       isDark: false,
 73 |       primaryColor: '#667eea',
 74 |       fontSize: 16
 75 |     }
 76 |   }
 77 | 
 78 |   // Provide theme to child components
 79 |   provide(ThemeKey, theme)
 80 | 
 81 |   return {
 82 |     theme,
 83 |     isDarkMode,
 84 |     themeClass,
 85 |     toggleDarkMode,
 86 |     setPrimaryColor,
 87 |     setFontSize,
 88 |     resetTheme
 89 |   }
 90 | }
 91 | 
 92 | /**
 93 |  * Composable for consuming theme in child components.
 94 |  * Demonstrates: inject pattern
 95 |  */
 96 | export function useTheme() {
 97 |   const theme = inject(ThemeKey)
 98 | 
 99 |   if (!theme) {
100 |     throw new Error('useTheme must be used within a component that provides ThemeKey')
101 |   }
102 | 
103 |   const isDark = computed(() => theme.value.isDark)
104 |   const primaryColor = computed(() => theme.value.primaryColor)
105 |   const fontSize = computed(() => theme.value.fontSize)
106 | 
107 |   return {
108 |     theme,
109 |     isDark,
110 |     primaryColor,
111 |     fontSize
112 |   }
113 | }
114 | 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # Base stage with common dependencies
 2 | FROM python:3.11-slim AS base
 3 | SHELL ["/bin/bash", "-c"]
 4 | 
 5 | # Set environment variables to make Python print directly to the terminal and avoid .pyc files.
 6 | ENV PYTHONUNBUFFERED=1
 7 | ENV PYTHONDONTWRITEBYTECODE=1
 8 | 
 9 | # Install system dependencies required for package manager and build tools.
10 | # sudo, wget, zip needed for some assistants, like junie
11 | RUN apt-get update && apt-get install -y --no-install-recommends \
12 |     curl \
13 |     build-essential \
14 |     git \
15 |     ssh \
16 |     sudo \
17 |     wget \
18 |     zip \
19 |     unzip \
20 |     git \
21 |     && rm -rf /var/lib/apt/lists/*
22 | 
23 | # Install pipx.
24 | RUN python3 -m pip install --no-cache-dir pipx \
25 |     && pipx ensurepath
26 | 
27 | # Install nodejs
28 | ENV NVM_VERSION=0.40.3
29 | ENV NODE_VERSION=22.18.0
30 | RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v${NVM_VERSION}/install.sh | bash
31 | # standard location
32 | ENV NVM_DIR=/root/.nvm
33 | RUN . "$NVM_DIR/nvm.sh" && nvm install ${NODE_VERSION}
34 | RUN . "$NVM_DIR/nvm.sh" && nvm use v${NODE_VERSION}
35 | RUN . "$NVM_DIR/nvm.sh" && nvm alias default v${NODE_VERSION}
36 | ENV PATH="${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH}"
37 | 
38 | # Add local bin to the path
39 | ENV PATH="${PATH}:/root/.local/bin"
40 | 
41 | # Install the latest version of uv
42 | RUN curl -LsSf https://astral.sh/uv/install.sh | sh
43 | 
44 | # Install Rust and rustup for rust-analyzer support (minimal profile)
45 | ENV RUSTUP_HOME=/usr/local/rustup
46 | ENV CARGO_HOME=/usr/local/cargo
47 | ENV PATH="${CARGO_HOME}/bin:${PATH}"
48 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
49 |     --default-toolchain stable \
50 |     --profile minimal \
51 |     && rustup component add rust-analyzer
52 | 
53 | # Set the working directory
54 | WORKDIR /workspaces/serena
55 | 
56 | # Copy all files for development
57 | COPY . /workspaces/serena/
58 | 
59 | # Install sed
60 | RUN apt-get update && apt-get install -y sed
61 | 
62 | # Create Serena configuration
63 | ENV SERENA_HOME=/workspaces/serena/config
64 | RUN mkdir -p $SERENA_HOME
65 | RUN cp src/serena/resources/serena_config.template.yml $SERENA_HOME/serena_config.yml
66 | RUN sed -i 's/^gui_log_window: .*/gui_log_window: False/' $SERENA_HOME/serena_config.yml
67 | RUN sed -i 's/^web_dashboard_listen_address: .*/web_dashboard_listen_address: 0.0.0.0/' $SERENA_HOME/serena_config.yml
68 | RUN sed -i 's/^web_dashboard_open_on_launch: .*/web_dashboard_open_on_launch: False/' $SERENA_HOME/serena_config.yml
69 | 
70 | # Create virtual environment and install dependencies
71 | RUN uv venv
72 | RUN . .venv/bin/activate
73 | RUN uv pip install -r pyproject.toml -e .
74 | ENV PATH="/workspaces/serena/.venv/bin:${PATH}"
75 | 
76 | # Entrypoint to ensure environment is activated
77 | ENTRYPOINT ["/bin/bash", "-c", "source .venv/bin/activate && $0 $@"]
78 | 
```

--------------------------------------------------------------------------------
/src/serena/resources/config/modes/editing.yml:
--------------------------------------------------------------------------------

```yaml
 1 | description: All tools, with detailed instructions for code editing
 2 | prompt: |
 3 |   You are operating in editing mode. You can edit files with the provided tools.
 4 |   You adhere to the project's code style and patterns.
 5 |   
 6 |   Use symbolic editing tools whenever possible for precise code modifications.
 7 |   If no explicit editing task has yet been provided, wait for the user to provide one. Do not be overly eager.
 8 | 
 9 |   When writing new code, think about where it belongs best. Don't generate new files if you don't plan on actually
10 |   properly integrating them into the codebase.
11 | 
12 |   You have two main approaches for editing code: (a) editing at the symbol level and (b) file-based editing.
13 |   The symbol-based approach is appropriate if you need to adjust an entire symbol, e.g. a method, a class, a function, etc.
14 |   It is not appropriate if you need to adjust just a few lines of code within a larger symbol.
15 | 
16 |   **Symbolic editing**
17 |   Use symbolic retrieval tools to identify the symbols you need to edit.
18 |   If you need to replace the definition of a symbol, use the `replace_symbol_body` tool.
19 |   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. 
20 |   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.
21 |   You can understand relationships between symbols by using the `find_referencing_symbols` tool. If not explicitly requested otherwise by the user,
22 |   you make sure that when you edit a symbol, the change is either backward-compatible or you find and update all references as needed.
23 |   The `find_referencing_symbols` tool will give you code snippets around the references as well as symbolic information.
24 |   You can assume that all symbol editing tools are reliable, so you never need to verify the results if the tools return without error.
25 | 
26 |   {% if 'replace_content' in available_tools %}
27 |   **File-based editing**
28 |   The `replace_content` tool allows you to perform regex-based replacements within files (as well as simple string replacements).
29 |   This is your primary tool for editing code whenever replacing or deleting a whole symbol would be a more expensive operation,
30 |   e.g. if you need to adjust just a few lines of code within a method.
31 |   You are extremely good at regex, so you never need to check whether the replacement produced the correct result.
32 |   In particular, you know how to use wildcards effectively in order to avoid specifying the full original text to be replaced!
33 |   {% endif %}
34 | excluded_tools:
35 |  - replace_lines
36 |  - insert_at_line
37 |  - delete_lines
38 | 
```

--------------------------------------------------------------------------------
/docs/03-special-guides/custom_agent.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Custom Agents with Serena
 2 | 
 3 | As a reference implementation, we provide an integration with the [Agno](https://docs.agno.com/introduction/playground) agent framework.
 4 | Agno is a model-agnostic agent framework that allows you to turn Serena into an agent 
 5 | (independent of the MCP technology) with a large number of underlying LLMs. While Agno has recently
 6 | added support for MCP servers out of the box, our Agno integration predates this and is a good illustration of how
 7 | easy it is to integrate Serena into an arbitrary agent framework.
 8 | 
 9 | Here's how it works:
10 | 
11 | 1. Download the agent-ui code with npx
12 |    ```shell
13 |    npx create-agent-ui@latest
14 |    ```
15 |    or, alternatively, clone it manually:
16 |    ```shell
17 |    git clone https://github.com/agno-agi/agent-ui.git
18 |    cd agent-ui 
19 |    pnpm install 
20 |    pnpm dev
21 |    ```
22 | 
23 | 2. Install serena with the optional requirements:
24 |    ```shell
25 |    # You can also only select agno,google or agno,anthropic instead of all-extras
26 |    uv pip install --all-extras -r pyproject.toml -e .
27 |    ```
28 |    
29 | 3. Copy `.env.example` to `.env` and fill in the API keys for the provider(s) you
30 |    intend to use.
31 | 
32 | 4. Start the agno agent app with
33 |    ```shell
34 |    uv run python scripts/agno_agent.py
35 |    ```
36 |    By default, the script uses Claude as the model, but you can choose any model
37 |    supported by Agno (which is essentially any existing model).
38 | 
39 | 5. In a new terminal, start the agno UI with
40 |    ```shell
41 |    cd agent-ui 
42 |    pnpm dev
43 |    ```
44 |    Connect the UI to the agent you started above and start chatting. You will have
45 |    the same tools as in the MCP server version.
46 | 
47 | 
48 | Here is a short demo of Serena performing a small analysis task with the newest Gemini model:
49 | 
50 | https://github.com/user-attachments/assets/ccfcb968-277d-4ca9-af7f-b84578858c62
51 | 
52 | 
53 | ⚠️ IMPORTANT: In contrast to the MCP server approach, tool execution in the Agno UI does
54 | not ask for the user's permission. The shell tool is particularly critical, as it can perform arbitrary code execution. 
55 | While we have never encountered any issues with
56 | this in our testing with Claude, allowing this may not be entirely safe. 
57 | You may choose to disable certain tools for your setup in your Serena project's
58 | configuration file (`.yml`).
59 | 
60 | 
61 | ## Other Agent Frameworks
62 | 
63 | It should be straightforward to incorporate Serena into any
64 | agent framework (like [pydantic-ai](https://ai.pydantic.dev/), [langgraph](https://langchain-ai.github.io/langgraph/tutorials/introduction/) or others).
65 | Typically, you need only to write an adapter for Serena's tools to the tool representation in the framework of your choice, 
66 | as was done by us for Agno with `SerenaAgnoToolkit` (see `/src/serena/agno.py`).
67 | 
68 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/python/test_retrieval_with_ignored_dirs.py:
--------------------------------------------------------------------------------

```python
 1 | from collections.abc import Generator
 2 | from pathlib import Path
 3 | 
 4 | import pytest
 5 | 
 6 | from solidlsp import SolidLanguageServer
 7 | from solidlsp.ls_config import Language
 8 | from test.conftest import start_ls_context
 9 | 
10 | # This mark will be applied to all tests in this module
11 | pytestmark = pytest.mark.python
12 | 
13 | 
14 | @pytest.fixture(scope="module")
15 | def ls_with_ignored_dirs() -> Generator[SolidLanguageServer, None, None]:
16 |     """Fixture to set up an LS for the python test repo with the 'scripts' directory ignored."""
17 |     ignored_paths = ["scripts", "custom_test"]
18 |     with start_ls_context(language=Language.PYTHON, ignored_paths=ignored_paths) as ls:
19 |         yield ls
20 | 
21 | 
22 | @pytest.mark.parametrize("ls_with_ignored_dirs", [Language.PYTHON], indirect=True)
23 | def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
24 |     """Tests that request_full_symbol_tree ignores the configured directory."""
25 |     root = ls_with_ignored_dirs.request_full_symbol_tree()[0]
26 |     root_children = root["children"]
27 |     children_names = {child["name"] for child in root_children}
28 |     assert children_names == {"test_repo", "examples"}
29 | 
30 | 
31 | @pytest.mark.parametrize("ls_with_ignored_dirs", [Language.PYTHON], indirect=True)
32 | def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
33 |     """Tests that find_references ignores the configured directory."""
34 |     # Location of Item, which is referenced in scripts
35 |     definition_file = "test_repo/models.py"
36 |     definition_line = 56
37 |     definition_col = 6
38 | 
39 |     references = ls_with_ignored_dirs.request_references(definition_file, definition_line, definition_col)
40 | 
41 |     # assert that scripts does not appear in the references
42 |     assert not any("scripts" in ref["relativePath"] for ref in references)
43 | 
44 | 
45 | @pytest.mark.parametrize("repo_path", [Language.PYTHON], indirect=True)
46 | def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
47 |     """Tests that refs and symbols with glob patterns are ignored."""
48 |     ignored_paths = ["*ipts", "custom_t*"]
49 |     with start_ls_context(language=Language.PYTHON, repo_path=str(repo_path), ignored_paths=ignored_paths) as ls:
50 |         # same as in the above tests
51 |         root = ls.request_full_symbol_tree()[0]
52 |         root_children = root["children"]
53 |         children_names = {child["name"] for child in root_children}
54 |         assert children_names == {"test_repo", "examples"}
55 | 
56 |         # test that the refs and symbols with glob patterns are ignored
57 |         definition_file = "test_repo/models.py"
58 |         definition_line = 56
59 |         definition_col = 6
60 | 
61 |         references = ls.request_references(definition_file, definition_line, definition_col)
62 |         assert not any("scripts" in ref["relativePath"] for ref in references)
63 | 
```

--------------------------------------------------------------------------------
/src/solidlsp/settings.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Defines settings for Solid-LSP
 3 | """
 4 | 
 5 | import logging
 6 | import os
 7 | import pathlib
 8 | from dataclasses import dataclass, field
 9 | from typing import TYPE_CHECKING, Any
10 | 
11 | from sensai.util.string import ToStringMixin
12 | 
13 | if TYPE_CHECKING:
14 |     from solidlsp.ls_config import Language
15 | 
16 | log = logging.getLogger(__name__)
17 | 
18 | 
19 | @dataclass
20 | class SolidLSPSettings:
21 |     solidlsp_dir: str = str(pathlib.Path.home() / ".solidlsp")
22 |     """
23 |     Path to the directory in which to store global Solid-LSP data (which is not project-specific)
24 |     """
25 |     project_data_relative_path: str = ".solidlsp"
26 |     """
27 |     Relative path within each project directory where Solid-LSP can store project-specific data, e.g. cache files.
28 |     For instance, if this is ".solidlsp" and the project is located at "/home/user/myproject",
29 |     then Solid-LSP will store project-specific data in "/home/user/myproject/.solidlsp".
30 |     """
31 |     ls_specific_settings: dict["Language", dict[str, Any]] = field(default_factory=dict)
32 |     """
33 |     Advanced configuration option allowing to configure language server implementation specific options.
34 |     Have a look at the docstring of the constructors of the corresponding LS implementations within solidlsp to see which options are available.
35 |     No documentation on options means no options are available.
36 |     """
37 | 
38 |     def __post_init__(self) -> None:
39 |         os.makedirs(str(self.solidlsp_dir), exist_ok=True)
40 |         os.makedirs(str(self.ls_resources_dir), exist_ok=True)
41 | 
42 |     @property
43 |     def ls_resources_dir(self) -> str:
44 |         return os.path.join(str(self.solidlsp_dir), "language_servers", "static")
45 | 
46 |     class CustomLSSettings(ToStringMixin):
47 |         def __init__(self, settings: dict[str, Any] | None) -> None:
48 |             self.settings = settings or {}
49 | 
50 |         def get(self, key: str, default_value: Any = None) -> Any:
51 |             """
52 |             Returns the custom setting for the given key or the default value if not set.
53 |             If a custom value is set for the given key, the retrieval is logged.
54 | 
55 |             :param key: the key
56 |             :param default_value: the default value to use if no custom value is set
57 |             :return: the value
58 |             """
59 |             if key in self.settings:
60 |                 value = self.settings[key]
61 |                 log.info("Using custom LS setting %s for key '%s'", value, key)
62 |             else:
63 |                 value = default_value
64 |             return value
65 | 
66 |     def get_ls_specific_settings(self, language: "Language") -> CustomLSSettings:
67 |         """
68 |         Get the language server specific settings for the given language.
69 | 
70 |         :param language: The programming language.
71 |         :return: A dictionary of settings for the language server.
72 |         """
73 |         return self.CustomLSSettings(self.ls_specific_settings.get(language))
74 | 
```

--------------------------------------------------------------------------------
/docs/02-usage/025_jetbrains_plugin.md:
--------------------------------------------------------------------------------

```markdown
 1 | # The Serena JetBrains Plugin
 2 | 
 3 | The [JetBrains Plugin](https://plugins.jetbrains.com/plugin/28946-serena/) allows Serena to
 4 | leverage the powerful code analysis and editing capabilities of your JetBrains IDE.
 5 | 
 6 | ```{raw} html
 7 | <p>
 8 | <a href="https://plugins.jetbrains.com/plugin/28946-serena/">
 9 | <img style="background-color:transparent;" src="../_static/images/jetbrains-marketplace-button.png">
10 | </a>
11 | </p>
12 | ```
13 | 
14 | We recommend the JetBrains plugin as the preferred way of using Serena,
15 | especially for users of JetBrains IDEs.
16 | 
17 | **Purchasing the JetBrains Plugin supports the Serena project.**
18 | The proceeds from plugin sales allow us to dedicate more resources to further developing and improving Serena.
19 | 
20 | ## Configuring Serena 
21 | 
22 | After installing the plugin, you need to configure Serena to use it.
23 | 
24 | **Central Configuration**.
25 | 
26 | Edit the global Serena configuration file located at `~/.serena/serena_config.yml` 
27 | (`%USERPROFILE%\.serena\serena_config.yml` on Windows).
28 | Change the `language_backend` setting as follows:
29 | 
30 | ```yaml
31 | language_backend: JetBrains
32 | ```
33 | 
34 | *Note*: you can also use the button `Edit Global Serena Config` in the Serena MCP dashboard to open the config file in your default editor.
35 | 
36 | **Per-Instance Configuration**.
37 | The configuration setting in the global config file can be overridden on a 
38 | per-instance basis by providing the arguments `--language-backend JetBrains` when 
39 | launching the Serena MCP server.
40 | 
41 | **Verifying the Setup**.
42 | You can verify that Serena is using the JetBrains plugin by either checking the dashboard, where
43 | you will see `Languages:
44 | Using JetBrains backend` in the configuration overview.
45 | You will also notice that your client will use the JetBrains-specific tools like `jet_brains_find_symbol` and others like it.
46 | 
47 | 
48 | ## Advantages of the JetBrains Plugin
49 | 
50 | There are multiple features that are only available when using the JetBrains plugin:
51 | 
52 | * **External library indexing**: Dependencies and libraries are fully indexed and accessible to Serena
53 | * **No additional setup**: No need to download or configure separate language servers
54 | * **Enhanced performance**: Faster tool execution thanks to optimized IDE integration
55 | * **Multi-language excellence**: First-class support for polyglot projects with multiple languages and frameworks
56 | 
57 | We are also working on additional features like a `move_symbol` tool and debugging-related capabilities that
58 | will be available exclusively through the JetBrains plugin.
59 | 
60 | ## Usage with Other Editors
61 | 
62 | We realize that not everyone uses a JetBrains IDE as their main code editor.
63 | You can still take advantage of the JetBrains plugin by running a JetBrains IDE instance alongside your
64 | preferred editor. Most JetBrains IDEs have a free community edition that you can use for this purpose.
65 | You just need to make sure that the project you are working on is open and indexed in the JetBrains IDE, 
66 | so that Serena can connect to it.
67 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/kotlin/test_kotlin_basic.py:
--------------------------------------------------------------------------------

```python
 1 | import os
 2 | 
 3 | import pytest
 4 | 
 5 | from solidlsp import SolidLanguageServer
 6 | from solidlsp.ls_config import Language
 7 | from solidlsp.ls_utils import SymbolUtils
 8 | 
 9 | 
10 | @pytest.mark.kotlin
11 | class TestKotlinLanguageServer:
12 |     @pytest.mark.parametrize("language_server", [Language.KOTLIN], indirect=True)
13 |     def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
14 |         symbols = language_server.request_full_symbol_tree()
15 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main class not found in symbol tree"
16 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils class not found in symbol tree"
17 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model class not found in symbol tree"
18 | 
19 |     @pytest.mark.parametrize("language_server", [Language.KOTLIN], indirect=True)
20 |     def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
21 |         # Use correct Kotlin file paths
22 |         file_path = os.path.join("src", "main", "kotlin", "test_repo", "Utils.kt")
23 |         refs = language_server.request_references(file_path, 3, 12)
24 |         assert any("Main.kt" in ref.get("relativePath", "") for ref in refs), "Main should reference Utils.printHello"
25 | 
26 |         # Dynamically determine the correct line/column for the 'Model' class name
27 |         file_path = os.path.join("src", "main", "kotlin", "test_repo", "Model.kt")
28 |         symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
29 |         model_symbol = None
30 |         for sym in symbols[0]:
31 |             print(sym)
32 |             print("\n")
33 |             if sym.get("name") == "Model" and sym.get("kind") == 23:  # 23 = Class
34 |                 model_symbol = sym
35 |                 break
36 |         assert model_symbol is not None, "Could not find 'Model' class symbol in Model.kt"
37 |         # Use selectionRange if present, otherwise fall back to range
38 |         if "selectionRange" in model_symbol:
39 |             sel_start = model_symbol["selectionRange"]["start"]
40 |         else:
41 |             sel_start = model_symbol["range"]["start"]
42 |         refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
43 |         assert any(
44 |             "Main.kt" in ref.get("relativePath", "") for ref in refs
45 |         ), "Main should reference Model (tried all positions in selectionRange)"
46 | 
47 |     @pytest.mark.parametrize("language_server", [Language.KOTLIN], indirect=True)
48 |     def test_overview_methods(self, language_server: SolidLanguageServer) -> None:
49 |         symbols = language_server.request_full_symbol_tree()
50 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main missing from overview"
51 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils missing from overview"
52 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model missing from overview"
53 | 
```

--------------------------------------------------------------------------------
/test/resources/repos/python/test_repo/test_repo/variables.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Test module for variable declarations and usage.
 3 | 
 4 | This module tests various types of variable declarations and usages including:
 5 | - Module-level variables
 6 | - Class-level variables
 7 | - Instance variables
 8 | - Variable reassignments
 9 | """
10 | 
11 | from dataclasses import dataclass, field
12 | 
13 | # Module-level variables
14 | module_var = "Initial module value"
15 | 
16 | reassignable_module_var = 10
17 | reassignable_module_var = 20  # Reassigned
18 | 
19 | # Module-level variable with type annotation
20 | typed_module_var: int = 42
21 | 
22 | 
23 | # Regular class with class and instance variables
24 | class VariableContainer:
25 |     """Class that contains various variables."""
26 | 
27 |     # Class-level variables
28 |     class_var = "Initial class value"
29 | 
30 |     reassignable_class_var = True
31 |     reassignable_class_var = False  # Reassigned #noqa: PIE794
32 | 
33 |     # Class-level variable with type annotation
34 |     typed_class_var: str = "typed value"
35 | 
36 |     def __init__(self):
37 |         # Instance variables
38 |         self.instance_var = "Initial instance value"
39 |         self.reassignable_instance_var = 100
40 | 
41 |         # Instance variable with type annotation
42 |         self.typed_instance_var: list[str] = ["item1", "item2"]
43 | 
44 |     def modify_instance_var(self):
45 |         # Reassign instance variable
46 |         self.instance_var = "Modified instance value"
47 |         self.reassignable_instance_var = 200  # Reassigned
48 | 
49 |     def use_module_var(self):
50 |         # Use module-level variables
51 |         result = module_var + " used in method"
52 |         other_result = reassignable_module_var + 5
53 |         return result, other_result
54 | 
55 |     def use_class_var(self):
56 |         # Use class-level variables
57 |         result = VariableContainer.class_var + " used in method"
58 |         other_result = VariableContainer.reassignable_class_var
59 |         return result, other_result
60 | 
61 | 
62 | # Dataclass with variables
63 | @dataclass
64 | class VariableDataclass:
65 |     """Dataclass that contains various fields."""
66 | 
67 |     # Field variables with type annotations
68 |     id: int
69 |     name: str
70 |     items: list[str] = field(default_factory=list)
71 |     metadata: dict[str, str] = field(default_factory=dict)
72 |     optional_value: float | None = None
73 | 
74 |     # This will be reassigned in various places
75 |     status: str = "pending"
76 | 
77 | 
78 | # Function that uses the module variables
79 | def use_module_variables():
80 |     """Function that uses module-level variables."""
81 |     result = module_var + " used in function"
82 |     other_result = reassignable_module_var * 2
83 |     return result, other_result
84 | 
85 | 
86 | # Create instances and use variables
87 | dataclass_instance = VariableDataclass(id=1, name="Test")
88 | dataclass_instance.status = "active"  # Reassign dataclass field
89 | 
90 | # Use variables at module level
91 | module_result = module_var + " used at module level"
92 | other_module_result = reassignable_module_var + 30
93 | 
94 | # Create a second dataclass instance with different status
95 | second_dataclass = VariableDataclass(id=2, name="Another Test")
96 | second_dataclass.status = "completed"  # Another reassignment of status
97 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/terraform/test_terraform_basic.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Basic integration tests for the Terraform language server functionality.
 3 | 
 4 | These tests validate the functionality of the language server APIs
 5 | like request_references using the test repository.
 6 | """
 7 | 
 8 | import pytest
 9 | 
10 | from solidlsp import SolidLanguageServer
11 | from solidlsp.ls_config import Language
12 | 
13 | 
14 | @pytest.mark.terraform
15 | class TestLanguageServerBasics:
16 |     """Test basic functionality of the Terraform language server."""
17 | 
18 |     @pytest.mark.parametrize("language_server", [Language.TERRAFORM], indirect=True)
19 |     def test_basic_definition(self, language_server: SolidLanguageServer) -> None:
20 |         """Test basic definition lookup functionality."""
21 |         # Simple test to verify the language server is working
22 |         file_path = "main.tf"
23 |         # Just try to get document symbols - this should work without hanging
24 |         symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
25 |         assert len(symbols) > 0, "Should find at least some symbols in main.tf"
26 | 
27 |     @pytest.mark.parametrize("language_server", [Language.TERRAFORM], indirect=True)
28 |     def test_request_references_aws_instance(self, language_server: SolidLanguageServer) -> None:
29 |         """Test request_references on an aws_instance resource."""
30 |         # Get references to an aws_instance resource in main.tf
31 |         file_path = "main.tf"
32 |         # Find aws_instance resources
33 |         symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
34 |         aws_instance_symbol = next((s for s in symbols[0] if s.get("name") == 'resource "aws_instance" "web_server"'), None)
35 |         if not aws_instance_symbol or "selectionRange" not in aws_instance_symbol:
36 |             raise AssertionError("aws_instance symbol or its selectionRange not found")
37 |         sel_start = aws_instance_symbol["selectionRange"]["start"]
38 |         references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
39 |         assert len(references) >= 1, "aws_instance should be referenced at least once"
40 | 
41 |     @pytest.mark.parametrize("language_server", [Language.TERRAFORM], indirect=True)
42 |     def test_request_references_variable(self, language_server: SolidLanguageServer) -> None:
43 |         """Test request_references on a variable."""
44 |         # Get references to a variable in variables.tf
45 |         file_path = "variables.tf"
46 |         # Find variable definitions
47 |         symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
48 |         var_symbol = next((s for s in symbols[0] if s.get("name") == 'variable "instance_type"'), None)
49 |         if not var_symbol or "selectionRange" not in var_symbol:
50 |             raise AssertionError("variable symbol or its selectionRange not found")
51 |         sel_start = var_symbol["selectionRange"]["start"]
52 |         references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
53 |         assert len(references) >= 1, "variable should be referenced at least once"
54 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/java/test_java_basic.py:
--------------------------------------------------------------------------------

```python
 1 | import os
 2 | 
 3 | import pytest
 4 | 
 5 | from solidlsp import SolidLanguageServer
 6 | from solidlsp.ls_config import Language
 7 | from solidlsp.ls_utils import SymbolUtils
 8 | from test.conftest import language_tests_enabled
 9 | 
10 | pytestmark = [pytest.mark.java, pytest.mark.skipif(not language_tests_enabled(Language.JAVA), reason="Java tests disabled")]
11 | 
12 | 
13 | class TestJavaLanguageServer:
14 |     @pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True)
15 |     def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
16 |         symbols = language_server.request_full_symbol_tree()
17 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main class not found in symbol tree"
18 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils class not found in symbol tree"
19 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model class not found in symbol tree"
20 | 
21 |     @pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True)
22 |     def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
23 |         # Use correct Maven/Java file paths
24 |         file_path = os.path.join("src", "main", "java", "test_repo", "Utils.java")
25 |         refs = language_server.request_references(file_path, 4, 20)
26 |         assert any("Main.java" in ref.get("relativePath", "") for ref in refs), "Main should reference Utils.printHello"
27 | 
28 |         # Dynamically determine the correct line/column for the 'Model' class name
29 |         file_path = os.path.join("src", "main", "java", "test_repo", "Model.java")
30 |         symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
31 |         model_symbol = None
32 |         for sym in symbols[0]:
33 |             if sym.get("name") == "Model" and sym.get("kind") == 5:  # 5 = Class
34 |                 model_symbol = sym
35 |                 break
36 |         assert model_symbol is not None, "Could not find 'Model' class symbol in Model.java"
37 |         # Use selectionRange if present, otherwise fall back to range
38 |         if "selectionRange" in model_symbol:
39 |             sel_start = model_symbol["selectionRange"]["start"]
40 |         else:
41 |             sel_start = model_symbol["range"]["start"]
42 |         refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
43 |         assert any(
44 |             "Main.java" in ref.get("relativePath", "") for ref in refs
45 |         ), "Main should reference Model (tried all positions in selectionRange)"
46 | 
47 |     @pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True)
48 |     def test_overview_methods(self, language_server: SolidLanguageServer) -> None:
49 |         symbols = language_server.request_full_symbol_tree()
50 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main missing from overview"
51 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils missing from overview"
52 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model missing from overview"
53 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/elm/test_elm_basic.py:
--------------------------------------------------------------------------------

```python
 1 | import os
 2 | 
 3 | import pytest
 4 | 
 5 | from solidlsp import SolidLanguageServer
 6 | from solidlsp.ls_config import Language
 7 | from solidlsp.ls_utils import SymbolUtils
 8 | 
 9 | 
10 | @pytest.mark.elm
11 | class TestElmLanguageServer:
12 |     @pytest.mark.parametrize("language_server", [Language.ELM], indirect=True)
13 |     def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
14 |         symbols = language_server.request_full_symbol_tree()
15 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "greet"), "greet function not found in symbol tree"
16 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "calculateSum"), "calculateSum function not found in symbol tree"
17 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "formatMessage"), "formatMessage function not found in symbol tree"
18 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "addNumbers"), "addNumbers function not found in symbol tree"
19 | 
20 |     @pytest.mark.parametrize("language_server", [Language.ELM], indirect=True)
21 |     def test_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
22 |         file_path = os.path.join("Main.elm")
23 |         symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
24 |         greet_symbol = None
25 |         for sym in symbols[0]:
26 |             if sym.get("name") == "greet":
27 |                 greet_symbol = sym
28 |                 break
29 |         assert greet_symbol is not None, "Could not find 'greet' symbol in Main.elm"
30 |         sel_start = greet_symbol["selectionRange"]["start"]
31 |         refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
32 |         assert any("Main.elm" in ref.get("relativePath", "") for ref in refs), "Main.elm should reference greet function"
33 | 
34 |     @pytest.mark.parametrize("language_server", [Language.ELM], indirect=True)
35 |     def test_find_references_across_files(self, language_server: SolidLanguageServer) -> None:
36 |         # Test formatMessage function which is defined in Utils.elm and used in Main.elm
37 |         utils_path = os.path.join("Utils.elm")
38 |         symbols = language_server.request_document_symbols(utils_path).get_all_symbols_and_roots()
39 |         formatMessage_symbol = None
40 |         for sym in symbols[0]:
41 |             if sym.get("name") == "formatMessage":
42 |                 formatMessage_symbol = sym
43 |                 break
44 |         assert formatMessage_symbol is not None, "Could not find 'formatMessage' symbol in Utils.elm"
45 | 
46 |         # Get references from the definition in Utils.elm
47 |         sel_start = formatMessage_symbol["selectionRange"]["start"]
48 |         refs = language_server.request_references(utils_path, sel_start["line"], sel_start["character"])
49 | 
50 |         # Verify that we found references
51 |         assert refs, "Expected to find references for formatMessage"
52 | 
53 |         # Verify that at least one reference is in Main.elm (where formatMessage is used)
54 |         assert any("Main.elm" in ref.get("relativePath", "") for ref in refs), "Expected to find usage of formatMessage in Main.elm"
55 | 
```

--------------------------------------------------------------------------------
/docs/03-special-guides/scala_setup_guide_for_serena.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Scala Setup Guide for Serena
 2 | 
 3 | This guide explains how to prepare a Scala project so that Serena can provide reliable code intelligence via Metals (Scala LSP) and how to run Scala tests manually.
 4 | 
 5 | Serena automatically bootstraps the Metals language server using Coursier when needed. Your project, however, must be importable by a build server (BSP) — typically via Bloop or sbt’s built‑in BSP — so that Metals can compile and index your code.
 6 | 
 7 | ---
 8 | ## Prerequisites
 9 | 
10 | Install the following on your system and ensure they are available on `PATH`:
11 | 
12 | - Java Development Kit (JDK). A modern LTS (e.g., 17 or 21) is recommended.
13 | - `sbt`
14 | - Coursier command (`cs`) or the legacy `coursier` launcher
15 |   - Serena uses `cs` if available; if only `coursier` exists, it will attempt to install `cs`. If neither is present, install Coursier first.
16 | 
17 | ---
18 | ## Quick Start (Recommended: VS Code + Metals auto‑import)
19 | 
20 | 1. Open your Scala project in VS Code.
21 | 2. When prompted by Metals, accept “Import build”. Wait until the import and initial compile/indexing finish.
22 | 3. Run the “Connect to build server” command (id: `build.connect`).
23 | 4. Once the import completes, start Serena in your project root and use it.
24 | 
25 | This flow ensures the `.bloop/` and (if applicable) `.metals/` directories are created and your build is known to the build server that Metals uses.
26 | 
27 | ---
28 | ## Manual Setup (No VS Code)
29 | 
30 | Follow these steps if you prefer a manual setup or you are not using VS Code:
31 | 
32 | These instructions cover the setup for projects that use sbt as the build tool, with Bloop as the BSP server.
33 | 
34 | 
35 | 1. Add Bloop to `project/plugins.sbt` in your Scala project:
36 |    ```scala
37 |    // project/plugins.sbt
38 |    addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "<version>")
39 |    ```
40 |    Replace `<version>` with an appropriate current version from the Metals documentation.
41 | 
42 | 2. Export Bloop configuration with sources:
43 |    ```bash
44 |    sbt -Dbloop.export-jar-classifiers=sources bloopInstall
45 |    ```
46 |    This creates a `.bloop/` directory containing your project’s build metadata for the BSP server.
47 | 
48 | 3. Compile from sbt to verify the build:
49 |    ```bash
50 |    sbt compile
51 |    ```
52 | 
53 | 4. Start Serena in your project root. Serena will bootstrap Metals (if not already present) and connect to the build server using the configuration exported above.
54 | 
55 | ---
56 | ## Using Serena with Scala
57 | 
58 | - Serena automatically detects Scala files (`*.scala`, `*.sbt`) and will start a Metals process per project when needed.
59 | - On first run, you may see messages like “Bootstrapping metals…” in the Serena logs — this is expected.
60 | - Optimal results require that your project compiles successfully via the build server (BSP). If compilation fails, fix build errors in `sbt` first.
61 | 
62 | 
63 | Notes:
64 | - Ensure you completed the manual or auto‑import steps so that the build is compiled and indexed; otherwise, code navigation and references may be incomplete until the first successful compile.
65 | 
66 | ## Reference 
67 | - Metals + sbt: [https://scalameta.org/metals/docs/build-tools/sbt](https://scalameta.org/metals/docs/build-tools/sbt)
```

--------------------------------------------------------------------------------
/test/solidlsp/rust/test_rust_basic.py:
--------------------------------------------------------------------------------

```python
 1 | import os
 2 | 
 3 | import pytest
 4 | 
 5 | from solidlsp import SolidLanguageServer
 6 | from solidlsp.ls_config import Language
 7 | from solidlsp.ls_utils import SymbolUtils
 8 | 
 9 | 
10 | @pytest.mark.rust
11 | class TestRustLanguageServer:
12 |     @pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
13 |     def test_find_references_raw(self, language_server: SolidLanguageServer) -> None:
14 |         # Directly test the request_references method for the add function
15 |         file_path = os.path.join("src", "lib.rs")
16 |         symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
17 |         add_symbol = None
18 |         for sym in symbols[0]:
19 |             if sym.get("name") == "add":
20 |                 add_symbol = sym
21 |                 break
22 |         assert add_symbol is not None, "Could not find 'add' function symbol in lib.rs"
23 |         sel_start = add_symbol["selectionRange"]["start"]
24 |         refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
25 |         assert any(
26 |             "main.rs" in ref.get("relativePath", "") for ref in refs
27 |         ), "main.rs should reference add (raw, tried all positions in selectionRange)"
28 | 
29 |     @pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
30 |     def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
31 |         symbols = language_server.request_full_symbol_tree()
32 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main function not found in symbol tree"
33 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add function not found in symbol tree"
34 |         # Add more as needed based on test_repo
35 | 
36 |     @pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
37 |     def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
38 |         # Find references to 'add' defined in lib.rs, should be referenced from main.rs
39 |         file_path = os.path.join("src", "lib.rs")
40 |         symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
41 |         add_symbol = None
42 |         for sym in symbols[0]:
43 |             if sym.get("name") == "add":
44 |                 add_symbol = sym
45 |                 break
46 |         assert add_symbol is not None, "Could not find 'add' function symbol in lib.rs"
47 |         sel_start = add_symbol["selectionRange"]["start"]
48 |         refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
49 |         assert any(
50 |             "main.rs" in ref.get("relativePath", "") for ref in refs
51 |         ), "main.rs should reference add (tried all positions in selectionRange)"
52 | 
53 |     @pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
54 |     def test_overview_methods(self, language_server: SolidLanguageServer) -> None:
55 |         symbols = language_server.request_full_symbol_tree()
56 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main missing from overview"
57 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add missing from overview"
58 | 
```

--------------------------------------------------------------------------------
/docs/03-special-guides/groovy_setup_guide_for_serena.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Groovy Setup Guide for Serena
 2 | 
 3 | The Groovy support in Serena is incomplete and requires the user to provide a functioning,
 4 | JVM-based Groovy language server as a jar. This intermediate state allows the contributors
 5 | of Groovy support to use Serena internally and hopefully to accelerate their open-source
 6 | release of a Groovy language server in the future.
 7 | 
 8 | If you happen to have a Groovy language server JAR file, you can configure Serena to use it
 9 | by following the instructions below.
10 | 
11 | ---
12 | ## Prerequisites
13 | 
14 | - Groovy Language Server JAR file
15 |     - Can be any open-source Groovy language server or your custom implementation
16 |     - The JAR must be compatible with standard LSP protocol
17 | 
18 | ---
19 | ## Configuration
20 | 
21 | Configure Groovy Language Server by adding settings to your `~/.serena/serena_config.yml`:
22 | 
23 | ### Basic Configuration
24 | 
25 | ```yaml
26 | ls_specific_settings:
27 |   groovy:
28 |     ls_jar_path: '/path/to/groovy-language-server.jar'
29 |     ls_jar_options: '-Xmx2G -Xms512m'
30 | ```
31 | 
32 | ### Custom Java Paths
33 | 
34 | If you have specific Java installations:
35 | 
36 | ```yaml
37 | ls_specific_settings:
38 |   groovy:
39 |     ls_jar_path: '/path/to/groovy-language-server.jar'
40 |     ls_java_home_path: '/usr/lib/jvm/java-21-openjdk'  # Custom JAVA_HOME directory
41 |     ls_jar_options: '-Xmx2G -Xms512m'                  # Optional JVM options
42 | ```
43 | 
44 | ### Configuration Options
45 | 
46 | - `ls_jar_path`: Absolute path to your Groovy Language Server JAR file (required)
47 | - `ls_java_home_path`: Custom JAVA_HOME directory for Java installation (optional)
48 |     - When specified, Serena will use this Java installation instead of downloading bundled Java
49 |     - Java executable path is automatically determined based on platform:
50 |         - Windows: `{ls_java_home_path}/bin/java.exe`
51 |         - Linux/macOS: `{ls_java_home_path}/bin/java`
52 |     - Validates that Java executable exists at the expected location
53 | - `ls_jar_options`: JVM options for language server (optional)
54 |     - Common options:
55 |         - `-Xmx<size>`: Maximum heap size (e.g., `-Xmx2G` for 2GB)
56 |         - `-Xms<size>`: Initial heap size (e.g., `-Xms512m` for 512MB)
57 | 
58 | ---
59 | ## Project Structure Requirements
60 | 
61 | For optimal Groovy Language Server performance, ensure your project follows standard Groovy/Gradle structure:
62 | 
63 | ```
64 | project-root/
65 | ├── src/
66 | │   ├── main/
67 | │   │   ├── groovy/
68 | │   │   └── resources/
69 | │   └── test/
70 | │       ├── groovy/
71 | │       └── resources/
72 | ├── build.gradle or build.gradle.kts
73 | ├── settings.gradle or settings.gradle.kts
74 | └── gradle/
75 |     └── wrapper/
76 | ```
77 | 
78 | ---
79 | ## Using Serena with Groovy
80 | 
81 | - Serena automatically detects Groovy files (`*.groovy`, `*.gvy`) and will start a Groovy Language Server JAR process per project when needed.
82 | - Optimal results require that your project compiles successfully via Gradle or Maven. If compilation fails, fix build errors in your build tool first.
83 | 
84 | ## Reference
85 | 
86 | - **Groovy Documentation**: [https://groovy-lang.org/documentation.html](https://groovy-lang.org/documentation.html)
87 | - **Gradle Documentation**: [https://docs.gradle.org](https://docs.gradle.org)
88 | - **Serena Configuration**: [../02-usage/050_configuration.md](../02-usage/050_configuration.md)
```

--------------------------------------------------------------------------------
/docs/03-special-guides/serena_on_chatgpt.md:
--------------------------------------------------------------------------------

```markdown
  1 | 
  2 | # Connecting Serena MCP Server to ChatGPT via MCPO & Cloudflare Tunnel
  3 | 
  4 | This guide explains how to expose a **locally running Serena MCP server** (powered by MCPO) to the internet using **Cloudflare Tunnel**, and how to connect it to **ChatGPT as a Custom GPT with tool access**.
  5 | 
  6 | Once configured, ChatGPT becomes a powerful **coding agent** with direct access to your codebase, shell, and file system — so **read the security notes carefully**.
  7 | 
  8 | ---
  9 | ## Prerequisites
 10 | 
 11 | Make sure you have [uv](https://docs.astral.sh/uv/getting-started/installation/) 
 12 | and [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) installed.
 13 | 
 14 | ## 1. Start the Serena MCP Server via MCPO
 15 | 
 16 | Run the following command to launch Serena as http server (assuming port 8000):
 17 | 
 18 | ```bash
 19 | uvx mcpo --port 8000 --api-key <YOUR_SECRET_KEY> -- \
 20 |   uvx --from git+https://github.com/oraios/serena \
 21 |   serena start-mcp-server --context chatgpt --project $(pwd)
 22 | ```
 23 | 
 24 | - `--api-key` is required to secure the server.
 25 | - `--project` should point to the root of your codebase.
 26 | 
 27 | You can also use other options, and you don't have to pass `--project` if you want to work on multiple projects
 28 | or want to activate it later. See 
 29 | 
 30 | ```shell
 31 | uvx --from git+https://github.com/oraios/serena serena start-mcp-server --help
 32 | ```
 33 | 
 34 | ---
 35 | 
 36 | ## 2. Expose the Server Using Cloudflare Tunnel
 37 | 
 38 | Run:
 39 | 
 40 | ```bash
 41 | cloudflared tunnel --url http://localhost:8000
 42 | ```
 43 | 
 44 | This will give you a **public HTTPS URL** like:
 45 | 
 46 | ```
 47 | https://serena-agent-tunnel.trycloudflare.com
 48 | ```
 49 | 
 50 | Your server is now securely exposed to the internet.
 51 | 
 52 | ---
 53 | 
 54 | ## 3. Connect It to ChatGPT (Custom GPT)
 55 | 
 56 | ### Steps:
 57 | 
 58 | 1. Go to [ChatGPT → Explore GPTs → Create](https://chat.openai.com/gpts/editor)
 59 | 2. During setup, click **“Add APIs”**
 60 | 3. Set up **API Key authentication** with the auth type as **Bearer** and enter the api key you used to start the MCPO server.
 61 | 4. In the **Schema** section, click on **import from URL** and paste `<cloudflared_url>/openapi.json` with the URL you got from the previous step.
 62 | 5. Add the following line to the top of the imported JSON schema:
 63 |     ```
 64 |      "servers": ["url": "<cloudflared_url>"],
 65 |     ```
 66 |    **Important**: don't include a trailing slash at the end of the URL!
 67 | 
 68 | ChatGPT will read the schema and create functions automatically.
 69 | 
 70 | ---
 71 | 
 72 | ## Security Warning — Read Carefully
 73 | 
 74 | Depending on your configuration and enabled tools, Serena's MCP server may:
 75 | - Execute **arbitrary shell commands**
 76 | - Read, write, and modify **files in your codebase**
 77 | 
 78 | This gives ChatGPT the same powers as a remote developer on your machine.
 79 | 
 80 | ### ⚠️ Key Rules:
 81 | - **NEVER expose your API key**
 82 | - **Only expose this server when needed**, and monitor its use.
 83 | 
 84 | In your project’s `.serena/project.yml` or global config, you can disable tools like:
 85 | 
 86 | ```yaml
 87 | excluded_tools:
 88 |   - execute_shell_command
 89 |   - ...
 90 | read_only: true
 91 | ```
 92 | 
 93 | This is strongly recommended if you want a read-only or safer agent.
 94 | 
 95 | 
 96 | ---
 97 | 
 98 | ## Final Thoughts
 99 | 
100 | With this setup, ChatGPT becomes a coding assistant **running on your local code** — able to index, search, edit, and even run shell commands depending on your configuration.
101 | 
102 | Use responsibly, and keep security in mind.
103 | 
```

--------------------------------------------------------------------------------
/test/resources/repos/ruby/test_repo/variables.rb:
--------------------------------------------------------------------------------

```ruby
  1 | require './models.rb'
  2 | 
  3 | # Global variables for testing references
  4 | $global_counter = 0
  5 | $global_config = {
  6 |   debug: true,
  7 |   timeout: 30
  8 | }
  9 | 
 10 | class DataContainer
 11 |   attr_accessor :status, :data, :metadata
 12 | 
 13 |   def initialize
 14 |     @status = "pending"
 15 |     @data = {}
 16 |     @metadata = {
 17 |       created_at: Time.now,
 18 |       version: "1.0"
 19 |     }
 20 |   end
 21 | 
 22 |   def update_status(new_status)
 23 |     old_status = @status
 24 |     @status = new_status
 25 |     log_status_change(old_status, new_status)
 26 |   end
 27 | 
 28 |   def process_data(input_data)
 29 |     @data = input_data
 30 |     @status = "processing"
 31 |     
 32 |     # Process the data
 33 |     result = @data.transform_values { |v| v.to_s.upcase }
 34 |     @status = "completed"
 35 |     
 36 |     result
 37 |   end
 38 | 
 39 |   def get_metadata_info
 40 |     info = "Status: #{@status}, Version: #{@metadata[:version]}"
 41 |     info += ", Created: #{@metadata[:created_at]}"
 42 |     info
 43 |   end
 44 | 
 45 |   private
 46 | 
 47 |   def log_status_change(old_status, new_status)
 48 |     puts "Status changed from #{old_status} to #{new_status}"
 49 |   end
 50 | end
 51 | 
 52 | class StatusTracker
 53 |   def initialize
 54 |     @tracked_items = []
 55 |   end
 56 | 
 57 |   def add_item(item)
 58 |     @tracked_items << item
 59 |     item.status = "tracked" if item.respond_to?(:status=)
 60 |   end
 61 | 
 62 |   def find_by_status(target_status)
 63 |     @tracked_items.select { |item| item.status == target_status }
 64 |   end
 65 | 
 66 |   def update_all_status(new_status)
 67 |     @tracked_items.each do |item|
 68 |       item.status = new_status if item.respond_to?(:status=)
 69 |     end
 70 |   end
 71 | end
 72 | 
 73 | # Module level variables and functions
 74 | module ProcessingHelper
 75 |   PROCESSING_MODES = ["sync", "async", "batch"].freeze
 76 |   
 77 |   @@instance_count = 0
 78 |   
 79 |   def self.create_processor(mode = "sync")
 80 |     @@instance_count += 1
 81 |     {
 82 |       id: @@instance_count,
 83 |       mode: mode,
 84 |       created_at: Time.now
 85 |     }
 86 |   end
 87 |   
 88 |   def self.get_instance_count
 89 |     @@instance_count
 90 |   end
 91 | end
 92 | 
 93 | # Test instances for reference testing
 94 | dataclass_instance = DataContainer.new
 95 | dataclass_instance.status = "initialized"
 96 | 
 97 | second_dataclass = DataContainer.new  
 98 | second_dataclass.update_status("ready")
 99 | 
100 | tracker = StatusTracker.new
101 | tracker.add_item(dataclass_instance)
102 | tracker.add_item(second_dataclass)
103 | 
104 | # Function that uses the variables
105 | def demonstrate_variable_usage
106 |   puts "Global counter: #{$global_counter}"
107 |   
108 |   container = DataContainer.new
109 |   container.status = "demo"
110 |   
111 |   processor = ProcessingHelper.create_processor("async")
112 |   puts "Created processor #{processor[:id]} in #{processor[:mode]} mode"
113 |   
114 |   container
115 | end
116 | 
117 | # More complex variable interactions
118 | class VariableInteractionTest
119 |   def initialize
120 |     @internal_status = "created"
121 |     @data_containers = []
122 |   end
123 |   
124 |   def add_container(container)
125 |     @data_containers << container
126 |     container.status = "added_to_collection"
127 |     @internal_status = "modified"
128 |   end
129 |   
130 |   def process_all_containers
131 |     @data_containers.each do |container|
132 |       container.status = "batch_processed"
133 |     end
134 |     @internal_status = "processing_complete"
135 |   end
136 |   
137 |   def get_status_summary
138 |     statuses = @data_containers.map(&:status)
139 |     {
140 |       internal: @internal_status,
141 |       containers: statuses,
142 |       count: @data_containers.length
143 |     }
144 |   end
145 | end
146 | 
147 | # Create instances for testing
148 | interaction_test = VariableInteractionTest.new
149 | interaction_test.add_container(dataclass_instance)
150 | interaction_test.add_container(second_dataclass)
```

--------------------------------------------------------------------------------
/src/serena/tools/config_tools.py:
--------------------------------------------------------------------------------

```python
 1 | from serena.config.context_mode import SerenaAgentMode
 2 | from serena.tools import Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional
 3 | 
 4 | 
 5 | class OpenDashboardTool(Tool, ToolMarkerOptional, ToolMarkerDoesNotRequireActiveProject):
 6 |     """
 7 |     Opens the Serena web dashboard in the default web browser.
 8 |     The dashboard provides logs, session information, and tool usage statistics.
 9 |     """
10 | 
11 |     def apply(self) -> str:
12 |         """
13 |         Opens the Serena web dashboard in the default web browser.
14 |         """
15 |         if self.agent.open_dashboard():
16 |             return f"Serena web dashboard has been opened in the user's default web browser: {self.agent.get_dashboard_url()}"
17 |         else:
18 |             return f"Serena web dashboard could not be opened automatically; tell the user to open it via {self.agent.get_dashboard_url()}"
19 | 
20 | 
21 | class ActivateProjectTool(Tool, ToolMarkerDoesNotRequireActiveProject):
22 |     """
23 |     Activates a project based on the project name or path.
24 |     """
25 | 
26 |     def apply(self, project: str) -> str:
27 |         """
28 |         Activates the project with the given name or path.
29 | 
30 |         :param project: the name of a registered project to activate or a path to a project directory
31 |         """
32 |         active_project = self.agent.activate_project_from_path_or_name(project)
33 |         result = active_project.get_activation_message()
34 |         result += "\nIMPORTANT: If you have not yet read the 'Serena Instructions Manual', do it now before continuing!"
35 |         return result
36 | 
37 | 
38 | class RemoveProjectTool(Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional):
39 |     """
40 |     Removes a project from the Serena configuration.
41 |     """
42 | 
43 |     def apply(self, project_name: str) -> str:
44 |         """
45 |         Removes a project from the Serena configuration.
46 | 
47 |         :param project_name: Name of the project to remove
48 |         """
49 |         self.agent.serena_config.remove_project(project_name)
50 |         return f"Successfully removed project '{project_name}' from configuration."
51 | 
52 | 
53 | class SwitchModesTool(Tool, ToolMarkerOptional):
54 |     """
55 |     Activates modes by providing a list of their names
56 |     """
57 | 
58 |     def apply(self, modes: list[str]) -> str:
59 |         """
60 |         Activates the desired modes, like ["editing", "interactive"] or ["planning", "one-shot"]
61 | 
62 |         :param modes: the names of the modes to activate
63 |         """
64 |         mode_instances = [SerenaAgentMode.load(mode) for mode in modes]
65 |         self.agent.set_modes(mode_instances)
66 | 
67 |         # Inform the Agent about the activated modes and the currently active tools
68 |         result_str = f"Successfully activated modes: {', '.join([mode.name for mode in mode_instances])}" + "\n"
69 |         result_str += "\n".join([mode_instance.prompt for mode_instance in mode_instances]) + "\n"
70 |         result_str += f"Currently active tools: {', '.join(self.agent.get_active_tool_names())}"
71 |         return result_str
72 | 
73 | 
74 | class GetCurrentConfigTool(Tool):
75 |     """
76 |     Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
77 |     """
78 | 
79 |     def apply(self) -> str:
80 |         """
81 |         Print the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
82 |         """
83 |         return self.agent.get_current_config_overview()
84 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/toml/test_toml_ignored_dirs.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Tests for TOML language server directory ignoring functionality.
 3 | 
 4 | These tests validate that the Taplo language server correctly ignores
 5 | TOML-specific directories like target, .cargo, and node_modules.
 6 | """
 7 | 
 8 | import pytest
 9 | 
10 | from solidlsp import SolidLanguageServer
11 | from solidlsp.ls_config import Language
12 | 
13 | pytestmark = pytest.mark.toml
14 | 
15 | 
16 | @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True)
17 | class TestTomlIgnoredDirectories:
18 |     """Test TOML-specific directory ignoring behavior."""
19 | 
20 |     def test_default_ignored_directories(self, language_server: SolidLanguageServer) -> None:
21 |         """Test that default TOML directories are ignored."""
22 |         # Test that TOML/Rust/Node-specific directories are ignored by default
23 |         assert language_server.is_ignored_dirname("target"), "target should be ignored"
24 |         assert language_server.is_ignored_dirname(".cargo"), ".cargo should be ignored"
25 |         assert language_server.is_ignored_dirname("node_modules"), "node_modules should be ignored"
26 | 
27 |         # Directories starting with . are ignored by base class
28 |         assert language_server.is_ignored_dirname(".git"), ".git should be ignored"
29 |         assert language_server.is_ignored_dirname(".venv"), ".venv should be ignored"
30 | 
31 |     def test_important_directories_not_ignored(self, language_server: SolidLanguageServer) -> None:
32 |         """Test that important directories are not ignored."""
33 |         # Common project directories should not be ignored
34 |         assert not language_server.is_ignored_dirname("src"), "src should not be ignored"
35 |         assert not language_server.is_ignored_dirname("crates"), "crates should not be ignored"
36 |         assert not language_server.is_ignored_dirname("lib"), "lib should not be ignored"
37 |         assert not language_server.is_ignored_dirname("tests"), "tests should not be ignored"
38 |         assert not language_server.is_ignored_dirname("config"), "config should not be ignored"
39 | 
40 |     def test_cargo_related_directories(self, language_server: SolidLanguageServer) -> None:
41 |         """Test Cargo/Rust-related directory handling."""
42 |         # Rust build directories should be ignored
43 |         assert language_server.is_ignored_dirname("target"), "target (Rust build) should be ignored"
44 |         assert language_server.is_ignored_dirname(".cargo"), ".cargo should be ignored"
45 | 
46 |         # But important Rust directories should not be ignored
47 |         assert not language_server.is_ignored_dirname("benches"), "benches should not be ignored"
48 |         assert not language_server.is_ignored_dirname("examples"), "examples should not be ignored"
49 | 
50 |     def test_various_cache_directories(self, language_server: SolidLanguageServer) -> None:
51 |         """Test various cache and temporary directories are ignored."""
52 |         # Directories starting with . are ignored by base class
53 |         assert language_server.is_ignored_dirname(".cache"), ".cache should be ignored"
54 | 
55 |         # IDE directories (start with .)
56 |         assert language_server.is_ignored_dirname(".idea"), ".idea should be ignored"
57 |         assert language_server.is_ignored_dirname(".vscode"), ".vscode should be ignored"
58 | 
59 |         # Note: __pycache__ is NOT ignored by TOML server (only Python servers ignore it)
60 |         assert not language_server.is_ignored_dirname("__pycache__"), "__pycache__ is not TOML-specific"
61 | 
```

--------------------------------------------------------------------------------
/test/resources/repos/python/test_repo/test_repo/utils.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Utility functions and classes demonstrating various Python features.
  3 | """
  4 | 
  5 | import logging
  6 | from collections.abc import Callable
  7 | from typing import Any, TypeVar
  8 | 
  9 | # Type variables for generic functions
 10 | T = TypeVar("T")
 11 | U = TypeVar("U")
 12 | 
 13 | 
 14 | def setup_logging(level: str = "INFO") -> logging.Logger:
 15 |     """Set up and return a configured logger"""
 16 |     levels = {
 17 |         "DEBUG": logging.DEBUG,
 18 |         "INFO": logging.INFO,
 19 |         "WARNING": logging.WARNING,
 20 |         "ERROR": logging.ERROR,
 21 |         "CRITICAL": logging.CRITICAL,
 22 |     }
 23 | 
 24 |     logger = logging.getLogger("test_repo")
 25 |     logger.setLevel(levels.get(level.upper(), logging.INFO))
 26 | 
 27 |     handler = logging.StreamHandler()
 28 |     formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
 29 |     handler.setFormatter(formatter)
 30 |     logger.addHandler(handler)
 31 | 
 32 |     return logger
 33 | 
 34 | 
 35 | # Decorator example
 36 | def log_execution(func: Callable) -> Callable:
 37 |     """Decorator to log function execution"""
 38 | 
 39 |     def wrapper(*args, **kwargs):
 40 |         logger = logging.getLogger("test_repo")
 41 |         logger.info(f"Executing function: {func.__name__}")
 42 |         result = func(*args, **kwargs)
 43 |         logger.info(f"Completed function: {func.__name__}")
 44 |         return result
 45 | 
 46 |     return wrapper
 47 | 
 48 | 
 49 | # Higher-order function
 50 | def map_list(items: list[T], mapper: Callable[[T], U]) -> list[U]:
 51 |     """Map a function over a list of items"""
 52 |     return [mapper(item) for item in items]
 53 | 
 54 | 
 55 | # Class with various Python features
 56 | class ConfigManager:
 57 |     """Manages configuration with various access patterns"""
 58 | 
 59 |     _instance = None
 60 | 
 61 |     # Singleton pattern
 62 |     def __new__(cls, *args, **kwargs):
 63 |         if not cls._instance:
 64 |             cls._instance = super().__new__(cls)
 65 |         return cls._instance
 66 | 
 67 |     def __init__(self, initial_config: dict[str, Any] | None = None):
 68 |         if not hasattr(self, "initialized"):
 69 |             self.config = initial_config or {}
 70 |             self.initialized = True
 71 | 
 72 |     def __getitem__(self, key: str) -> Any:
 73 |         """Allow dictionary-like access"""
 74 |         return self.config.get(key)
 75 | 
 76 |     def __setitem__(self, key: str, value: Any) -> None:
 77 |         """Allow dictionary-like setting"""
 78 |         self.config[key] = value
 79 | 
 80 |     @property
 81 |     def debug_mode(self) -> bool:
 82 |         """Property example"""
 83 |         return self.config.get("debug", False)
 84 | 
 85 |     @debug_mode.setter
 86 |     def debug_mode(self, value: bool) -> None:
 87 |         self.config["debug"] = value
 88 | 
 89 | 
 90 | # Context manager example
 91 | class Timer:
 92 |     """Context manager for timing code execution"""
 93 | 
 94 |     def __init__(self, name: str = "Timer"):
 95 |         self.name = name
 96 |         self.start_time = None
 97 |         self.end_time = None
 98 | 
 99 |     def __enter__(self):
100 |         import time
101 | 
102 |         self.start_time = time.time()
103 |         return self
104 | 
105 |     def __exit__(self, exc_type, exc_val, exc_tb):
106 |         import time
107 | 
108 |         self.end_time = time.time()
109 |         print(f"{self.name} took {self.end_time - self.start_time:.6f} seconds")
110 | 
111 | 
112 | # Functions with default arguments
113 | def retry(func: Callable, max_attempts: int = 3, delay: float = 1.0) -> Any:
114 |     """Retry a function with backoff"""
115 |     import time
116 | 
117 |     for attempt in range(max_attempts):
118 |         try:
119 |             return func()
120 |         except Exception as e:
121 |             if attempt == max_attempts - 1:
122 |                 raise e
123 |             time.sleep(delay * (2**attempt))
124 | 
```

--------------------------------------------------------------------------------
/test/resources/repos/vue/test_repo/src/composables/useFormatter.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ref, computed } from 'vue'
  2 | import type { Ref, ComputedRef } from 'vue'
  3 | import type { FormatOptions } from '@/types'
  4 | 
  5 | /**
  6 |  * Composable for formatting numbers with various options.
  7 |  * Demonstrates: composable pattern, refs, computed, type imports
  8 |  */
  9 | export function useFormatter(initialPrecision: number = 2) {
 10 |   // State
 11 |   const precision = ref<number>(initialPrecision)
 12 |   const useGrouping = ref<boolean>(true)
 13 |   const locale = ref<string>('en-US')
 14 | 
 15 |   // Computed properties
 16 |   const formatOptions = computed((): FormatOptions => ({
 17 |     maxDecimals: precision.value,
 18 |     useGrouping: useGrouping.value
 19 |   }))
 20 | 
 21 |   // Methods
 22 |   const formatNumber = (value: number): string => {
 23 |     return value.toLocaleString(locale.value, {
 24 |       minimumFractionDigits: precision.value,
 25 |       maximumFractionDigits: precision.value,
 26 |       useGrouping: useGrouping.value
 27 |     })
 28 |   }
 29 | 
 30 |   const formatCurrency = (value: number, currency: string = 'USD'): string => {
 31 |     return value.toLocaleString(locale.value, {
 32 |       style: 'currency',
 33 |       currency,
 34 |       minimumFractionDigits: precision.value,
 35 |       maximumFractionDigits: precision.value
 36 |     })
 37 |   }
 38 | 
 39 |   const formatPercentage = (value: number): string => {
 40 |     return `${(value * 100).toFixed(precision.value)}%`
 41 |   }
 42 | 
 43 |   const setPrecision = (newPrecision: number): void => {
 44 |     if (newPrecision >= 0 && newPrecision <= 10) {
 45 |       precision.value = newPrecision
 46 |     }
 47 |   }
 48 | 
 49 |   const toggleGrouping = (): void => {
 50 |     useGrouping.value = !useGrouping.value
 51 |   }
 52 | 
 53 |   const setLocale = (newLocale: string): void => {
 54 |     locale.value = newLocale
 55 |   }
 56 | 
 57 |   // Return composable API
 58 |   return {
 59 |     // State (readonly)
 60 |     precision: computed(() => precision.value),
 61 |     useGrouping: computed(() => useGrouping.value),
 62 |     locale: computed(() => locale.value),
 63 |     formatOptions,
 64 | 
 65 |     // Methods
 66 |     formatNumber,
 67 |     formatCurrency,
 68 |     formatPercentage,
 69 |     setPrecision,
 70 |     toggleGrouping,
 71 |     setLocale
 72 |   }
 73 | }
 74 | 
 75 | /**
 76 |  * Composable for time formatting.
 77 |  * Demonstrates: simpler composable, pure functions
 78 |  */
 79 | export function useTimeFormatter() {
 80 |   const formatTime = (date: Date): string => {
 81 |     return date.toLocaleTimeString('en-US', {
 82 |       hour: '2-digit',
 83 |       minute: '2-digit',
 84 |       second: '2-digit'
 85 |     })
 86 |   }
 87 | 
 88 |   const formatDate = (date: Date): string => {
 89 |     return date.toLocaleDateString('en-US', {
 90 |       year: 'numeric',
 91 |       month: 'long',
 92 |       day: 'numeric'
 93 |     })
 94 |   }
 95 | 
 96 |   const formatDateTime = (date: Date): string => {
 97 |     return `${formatDate(date)} ${formatTime(date)}`
 98 |   }
 99 | 
100 |   const getRelativeTime = (date: Date): string => {
101 |     const now = new Date()
102 |     const diffMs = now.getTime() - date.getTime()
103 |     const diffSecs = Math.floor(diffMs / 1000)
104 |     const diffMins = Math.floor(diffSecs / 60)
105 |     const diffHours = Math.floor(diffMins / 60)
106 |     const diffDays = Math.floor(diffHours / 24)
107 | 
108 |     if (diffSecs < 60) return 'just now'
109 |     if (diffMins < 60) return `${diffMins} minute${diffMins > 1 ? 's' : ''} ago`
110 |     if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`
111 |     return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`
112 |   }
113 | 
114 |   return {
115 |     formatTime,
116 |     formatDate,
117 |     formatDateTime,
118 |     getRelativeTime
119 |   }
120 | }
121 | 
122 | /**
123 |  * Type definitions for return types
124 |  */
125 | export type UseFormatterReturn = ReturnType<typeof useFormatter>
126 | export type UseTimeFormatterReturn = ReturnType<typeof useTimeFormatter>
127 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/markdown/test_markdown_basic.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Basic integration tests for the markdown language server functionality.
 3 | 
 4 | These tests validate the functionality of the language server APIs
 5 | like request_document_symbols using the markdown test repository.
 6 | """
 7 | 
 8 | import pytest
 9 | 
10 | from solidlsp import SolidLanguageServer
11 | from solidlsp.ls_config import Language
12 | 
13 | 
14 | @pytest.mark.markdown
15 | class TestMarkdownLanguageServerBasics:
16 |     """Test basic functionality of the markdown language server."""
17 | 
18 |     @pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
19 |     def test_markdown_language_server_initialization(self, language_server: SolidLanguageServer) -> None:
20 |         """Test that markdown language server can be initialized successfully."""
21 |         assert language_server is not None
22 |         assert language_server.language == Language.MARKDOWN
23 | 
24 |     @pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
25 |     def test_markdown_request_document_symbols(self, language_server: SolidLanguageServer) -> None:
26 |         """Test request_document_symbols for markdown files."""
27 |         # Test getting symbols from README.md
28 |         all_symbols, _root_symbols = language_server.request_document_symbols("README.md").get_all_symbols_and_roots()
29 | 
30 |         # Extract heading symbols (LSP Symbol Kind 15 is String, but marksman uses kind 15 for headings)
31 |         # Note: Different markdown LSPs may use different symbol kinds for headings
32 |         # Marksman typically uses kind 15 (String) for markdown headings
33 |         heading_names = [symbol["name"] for symbol in all_symbols]
34 | 
35 |         # Should detect headings from README.md
36 |         assert "Test Repository" in heading_names or len(all_symbols) > 0, "Should find at least one heading"
37 | 
38 |     @pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
39 |     def test_markdown_request_symbols_from_guide(self, language_server: SolidLanguageServer) -> None:
40 |         """Test symbol detection in guide.md file."""
41 |         all_symbols, _root_symbols = language_server.request_document_symbols("guide.md").get_all_symbols_and_roots()
42 | 
43 |         # At least some headings should be found
44 |         assert len(all_symbols) > 0, f"Should find headings in guide.md, found {len(all_symbols)}"
45 | 
46 |     @pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
47 |     def test_markdown_request_symbols_from_api(self, language_server: SolidLanguageServer) -> None:
48 |         """Test symbol detection in api.md file."""
49 |         all_symbols, _root_symbols = language_server.request_document_symbols("api.md").get_all_symbols_and_roots()
50 | 
51 |         # Should detect headings from api.md
52 |         assert len(all_symbols) > 0, f"Should find headings in api.md, found {len(all_symbols)}"
53 | 
54 |     @pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
55 |     def test_markdown_request_document_symbols_with_body(self, language_server: SolidLanguageServer) -> None:
56 |         """Test request_document_symbols with body extraction."""
57 |         # Test with include_body=True
58 |         all_symbols, _root_symbols = language_server.request_document_symbols("README.md").get_all_symbols_and_roots()
59 | 
60 |         # Should have found some symbols
61 |         assert len(all_symbols) > 0, "Should find symbols in README.md"
62 | 
63 |         # Note: Not all markdown LSPs provide body information for symbols
64 |         # This test is more lenient and just verifies the API works
65 |         assert all_symbols is not None, "Should return symbols even if body extraction is limited"
66 | 
```

--------------------------------------------------------------------------------
/src/serena/tools/memory_tools.py:
--------------------------------------------------------------------------------

```python
 1 | from typing import Literal
 2 | 
 3 | from serena.tools import ReplaceContentTool, Tool, ToolMarkerCanEdit
 4 | 
 5 | 
 6 | class WriteMemoryTool(Tool, ToolMarkerCanEdit):
 7 |     """
 8 |     Writes a named memory (for future reference) to Serena's project-specific memory store.
 9 |     """
10 | 
11 |     def apply(self, memory_file_name: str, content: str, max_answer_chars: int = -1) -> str:
12 |         """
13 |         Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format.
14 |         The memory name should be meaningful.
15 |         """
16 |         # NOTE: utf-8 encoding is configured in the MemoriesManager
17 |         if max_answer_chars == -1:
18 |             max_answer_chars = self.agent.serena_config.default_max_tool_answer_chars
19 |         if len(content) > max_answer_chars:
20 |             raise ValueError(
21 |                 f"Content for {memory_file_name} is too long. Max length is {max_answer_chars} characters. "
22 |                 + "Please make the content shorter."
23 |             )
24 | 
25 |         return self.memories_manager.save_memory(memory_file_name, content)
26 | 
27 | 
28 | class ReadMemoryTool(Tool):
29 |     """
30 |     Reads the memory with the given name from Serena's project-specific memory store.
31 |     """
32 | 
33 |     def apply(self, memory_file_name: str, max_answer_chars: int = -1) -> str:
34 |         """
35 |         Read the content of a memory file. This tool should only be used if the information
36 |         is relevant to the current task. You can infer whether the information
37 |         is relevant from the memory file name.
38 |         You should not read the same memory file multiple times in the same conversation.
39 |         """
40 |         return self.memories_manager.load_memory(memory_file_name)
41 | 
42 | 
43 | class ListMemoriesTool(Tool):
44 |     """
45 |     Lists memories in Serena's project-specific memory store.
46 |     """
47 | 
48 |     def apply(self) -> str:
49 |         """
50 |         List available memories. Any memory can be read using the `read_memory` tool.
51 |         """
52 |         return self._to_json(self.memories_manager.list_memories())
53 | 
54 | 
55 | class DeleteMemoryTool(Tool, ToolMarkerCanEdit):
56 |     """
57 |     Deletes a memory from Serena's project-specific memory store.
58 |     """
59 | 
60 |     def apply(self, memory_file_name: str) -> str:
61 |         """
62 |         Delete a memory file. Should only happen if a user asks for it explicitly,
63 |         for example by saying that the information retrieved from a memory file is no longer correct
64 |         or no longer relevant for the project.
65 |         """
66 |         return self.memories_manager.delete_memory(memory_file_name)
67 | 
68 | 
69 | class EditMemoryTool(Tool, ToolMarkerCanEdit):
70 |     def apply(
71 |         self,
72 |         memory_file_name: str,
73 |         needle: str,
74 |         repl: str,
75 |         mode: Literal["literal", "regex"],
76 |     ) -> str:
77 |         r"""
78 |         Replaces content matching a regular expression in a memory.
79 | 
80 |         :param memory_file_name: the name of the memory
81 |         :param needle: the string or regex pattern to search for.
82 |             If `mode` is "literal", this string will be matched exactly.
83 |             If `mode` is "regex", this string will be treated as a regular expression (syntax of Python's `re` module,
84 |             with flags DOTALL and MULTILINE enabled).
85 |         :param repl: the replacement string (verbatim).
86 |         :param mode: either "literal" or "regex", specifying how the `needle` parameter is to be interpreted.
87 |         """
88 |         replace_content_tool = self.agent.get_tool(ReplaceContentTool)
89 |         rel_path = self.memories_manager.get_memory_file_path(memory_file_name).relative_to(self.get_project_root())
90 |         return replace_content_tool.replace_content(str(rel_path), needle, repl, mode=mode, require_not_ignored=False)
91 | 
```

--------------------------------------------------------------------------------
/test/serena/test_task_executor.py:
--------------------------------------------------------------------------------

```python
  1 | import time
  2 | 
  3 | import pytest
  4 | 
  5 | from serena.task_executor import TaskExecutor
  6 | 
  7 | 
  8 | @pytest.fixture
  9 | def executor():
 10 |     """
 11 |     Fixture for a basic SerenaAgent without a project
 12 |     """
 13 |     return TaskExecutor("TestExecutor")
 14 | 
 15 | 
 16 | class Task:
 17 |     def __init__(self, delay: float, exception: bool = False):
 18 |         self.delay = delay
 19 |         self.exception = exception
 20 |         self.did_run = False
 21 | 
 22 |     def run(self):
 23 |         self.did_run = True
 24 |         time.sleep(self.delay)
 25 |         if self.exception:
 26 |             raise ValueError("Task failed")
 27 |         return True
 28 | 
 29 | 
 30 | def test_task_executor_sequence(executor):
 31 |     """
 32 |     Tests that a sequence of tasks is executed correctly
 33 |     """
 34 |     future1 = executor.issue_task(Task(1).run, name="task1")
 35 |     future2 = executor.issue_task(Task(1).run, name="task2")
 36 |     assert future1.result() is True
 37 |     assert future2.result() is True
 38 | 
 39 | 
 40 | def test_task_executor_exception(executor):
 41 |     """
 42 |     Tests that tasks that raise exceptions are handled correctly, i.e. that
 43 |       * the exception is propagated,
 44 |       * subsequent tasks are still executed.
 45 |     """
 46 |     future1 = executor.issue_task(Task(1, exception=True).run, name="task1")
 47 |     future2 = executor.issue_task(Task(1).run, name="task2")
 48 |     have_exception = False
 49 |     try:
 50 |         assert future1.result()
 51 |     except Exception as e:
 52 |         assert isinstance(e, ValueError)
 53 |         have_exception = True
 54 |     assert have_exception
 55 |     assert future2.result() is True
 56 | 
 57 | 
 58 | def test_task_executor_cancel_current(executor):
 59 |     """
 60 |     Tests that tasks that are cancelled are handled correctly, i.e. that
 61 |       * subsequent tasks are executed as soon as cancellation ensues.
 62 |       * the cancelled task raises CancelledError when result() is called.
 63 |     """
 64 |     start_time = time.time()
 65 |     future1 = executor.issue_task(Task(10).run, name="task1")
 66 |     future2 = executor.issue_task(Task(1).run, name="task2")
 67 |     time.sleep(1)
 68 |     future1.cancel()
 69 |     assert future2.result() is True
 70 |     end_time = time.time()
 71 |     assert (end_time - start_time) < 9, "Cancelled task did not stop in time"
 72 |     have_cancelled_error = False
 73 |     try:
 74 |         future1.result()
 75 |     except Exception as e:
 76 |         assert e.__class__.__name__ == "CancelledError"
 77 |         have_cancelled_error = True
 78 |     assert have_cancelled_error
 79 | 
 80 | 
 81 | def test_task_executor_cancel_future(executor):
 82 |     """
 83 |     Tests that when a future task is cancelled, it is never run at all
 84 |     """
 85 |     task1 = Task(10)
 86 |     task2 = Task(1)
 87 |     future1 = executor.issue_task(task1.run, name="task1")
 88 |     future2 = executor.issue_task(task2.run, name="task2")
 89 |     time.sleep(1)
 90 |     future2.cancel()
 91 |     future1.cancel()
 92 |     try:
 93 |         future2.result()
 94 |     except:
 95 |         pass
 96 |     assert task1.did_run
 97 |     assert not task2.did_run
 98 | 
 99 | 
100 | def test_task_executor_cancellation_via_task_info(executor):
101 |     start_time = time.time()
102 |     executor.issue_task(Task(10).run, "task1")
103 |     executor.issue_task(Task(10).run, "task2")
104 |     task_infos = executor.get_current_tasks()
105 |     task_infos2 = executor.get_current_tasks()
106 | 
107 |     # test expected tasks
108 |     assert len(task_infos) == 2
109 |     assert "task1" in task_infos[0].name
110 |     assert "task2" in task_infos[1].name
111 | 
112 |     # test task identifiers being stable
113 |     assert task_infos2[0].task_id == task_infos[0].task_id
114 | 
115 |     # test cancellation
116 |     task_infos[0].cancel()
117 |     time.sleep(0.5)
118 |     task_infos3 = executor.get_current_tasks()
119 |     assert len(task_infos3) == 1  # Cancelled task is gone from the queue
120 |     task_infos3[0].cancel()
121 |     try:
122 |         task_infos3[0].future.result()
123 |     except:
124 |         pass
125 |     end_time = time.time()
126 |     assert (end_time - start_time) < 9, "Cancelled task did not stop in time"
127 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/util/test_zip.py:
--------------------------------------------------------------------------------

```python
  1 | import sys
  2 | import zipfile
  3 | from pathlib import Path
  4 | 
  5 | import pytest
  6 | 
  7 | from solidlsp.util.zip import SafeZipExtractor
  8 | 
  9 | 
 10 | @pytest.fixture
 11 | def temp_zip_file(tmp_path: Path) -> Path:
 12 |     """Create a temporary ZIP file for testing."""
 13 |     zip_path = tmp_path / "test.zip"
 14 |     with zipfile.ZipFile(zip_path, "w") as zipf:
 15 |         zipf.writestr("file1.txt", "Hello World 1")
 16 |         zipf.writestr("file2.txt", "Hello World 2")
 17 |         zipf.writestr("folder/file3.txt", "Hello World 3")
 18 |     return zip_path
 19 | 
 20 | 
 21 | def test_extract_all_success(temp_zip_file: Path, tmp_path: Path) -> None:
 22 |     """All files should extract without error."""
 23 |     dest_dir = tmp_path / "extracted"
 24 |     extractor = SafeZipExtractor(temp_zip_file, dest_dir, verbose=False)
 25 |     extractor.extract_all()
 26 | 
 27 |     assert (dest_dir / "file1.txt").read_text() == "Hello World 1"
 28 |     assert (dest_dir / "file2.txt").read_text() == "Hello World 2"
 29 |     assert (dest_dir / "folder" / "file3.txt").read_text() == "Hello World 3"
 30 | 
 31 | 
 32 | def test_include_patterns(temp_zip_file: Path, tmp_path: Path) -> None:
 33 |     """Only files matching include_patterns should be extracted."""
 34 |     dest_dir = tmp_path / "extracted"
 35 |     extractor = SafeZipExtractor(temp_zip_file, dest_dir, verbose=False, include_patterns=["*.txt"])
 36 |     extractor.extract_all()
 37 | 
 38 |     assert (dest_dir / "file1.txt").exists()
 39 |     assert (dest_dir / "file2.txt").exists()
 40 |     assert (dest_dir / "folder" / "file3.txt").exists()
 41 | 
 42 | 
 43 | def test_exclude_patterns(temp_zip_file: Path, tmp_path: Path) -> None:
 44 |     """Files matching exclude_patterns should be skipped."""
 45 |     dest_dir = tmp_path / "extracted"
 46 |     extractor = SafeZipExtractor(temp_zip_file, dest_dir, verbose=False, exclude_patterns=["file2.txt"])
 47 |     extractor.extract_all()
 48 | 
 49 |     assert (dest_dir / "file1.txt").exists()
 50 |     assert not (dest_dir / "file2.txt").exists()
 51 |     assert (dest_dir / "folder" / "file3.txt").exists()
 52 | 
 53 | 
 54 | def test_include_and_exclude_patterns(temp_zip_file: Path, tmp_path: Path) -> None:
 55 |     """Exclude should override include if both match."""
 56 |     dest_dir = tmp_path / "extracted"
 57 |     extractor = SafeZipExtractor(
 58 |         temp_zip_file,
 59 |         dest_dir,
 60 |         verbose=False,
 61 |         include_patterns=["*.txt"],
 62 |         exclude_patterns=["file1.txt"],
 63 |     )
 64 |     extractor.extract_all()
 65 | 
 66 |     assert not (dest_dir / "file1.txt").exists()
 67 |     assert (dest_dir / "file2.txt").exists()
 68 |     assert (dest_dir / "folder" / "file3.txt").exists()
 69 | 
 70 | 
 71 | def test_skip_on_error(monkeypatch, temp_zip_file: Path, tmp_path: Path) -> None:
 72 |     """Should skip a file that raises an error and continue extracting others."""
 73 |     dest_dir = tmp_path / "extracted"
 74 | 
 75 |     original_open = zipfile.ZipFile.open
 76 | 
 77 |     def failing_open(self, member, *args, **kwargs):
 78 |         if member.filename == "file2.txt":
 79 |             raise OSError("Simulated failure")
 80 |         return original_open(self, member, *args, **kwargs)
 81 | 
 82 |     # Patch the method on the class, not on an instance
 83 |     monkeypatch.setattr(zipfile.ZipFile, "open", failing_open)
 84 | 
 85 |     extractor = SafeZipExtractor(temp_zip_file, dest_dir, verbose=False)
 86 |     extractor.extract_all()
 87 | 
 88 |     assert (dest_dir / "file1.txt").exists()
 89 |     assert not (dest_dir / "file2.txt").exists()
 90 |     assert (dest_dir / "folder" / "file3.txt").exists()
 91 | 
 92 | 
 93 | @pytest.mark.skipif(not sys.platform.startswith("win"), reason="Windows-only test")
 94 | def test_long_path_normalization(temp_zip_file: Path, tmp_path: Path) -> None:
 95 |     r"""Ensure _normalize_path adds \\?\\ prefix on Windows."""
 96 |     dest_dir = tmp_path / ("a" * 250)  # Simulate long path
 97 |     extractor = SafeZipExtractor(temp_zip_file, dest_dir, verbose=False)
 98 |     norm_path = extractor._normalize_path(dest_dir / "file.txt")
 99 |     assert str(norm_path).startswith("\\\\?\\")
100 | 
```

--------------------------------------------------------------------------------
/src/serena/util/logging.py:
--------------------------------------------------------------------------------

```python
  1 | import queue
  2 | import threading
  3 | from collections.abc import Callable
  4 | from typing import Optional
  5 | 
  6 | from sensai.util import logging
  7 | 
  8 | from serena.constants import SERENA_LOG_FORMAT
  9 | 
 10 | lg = logging
 11 | 
 12 | 
 13 | class MemoryLogHandler(logging.Handler):
 14 |     def __init__(self, level: int = logging.NOTSET) -> None:
 15 |         super().__init__(level=level)
 16 |         self.setFormatter(logging.Formatter(SERENA_LOG_FORMAT))
 17 |         self._log_buffer = LogBuffer()
 18 |         self._log_queue: queue.Queue[str] = queue.Queue()
 19 |         self._stop_event = threading.Event()
 20 |         self._emit_callbacks: list[Callable[[str], None]] = []
 21 | 
 22 |         # start background thread to process logs
 23 |         self.worker_thread = threading.Thread(target=self._process_queue, daemon=True)
 24 |         self.worker_thread.start()
 25 | 
 26 |     def add_emit_callback(self, callback: Callable[[str], None]) -> None:
 27 |         """
 28 |         Adds a callback that will be called with each log message.
 29 |         The callback should accept a single string argument (the log message).
 30 |         """
 31 |         self._emit_callbacks.append(callback)
 32 | 
 33 |     def emit(self, record: logging.LogRecord) -> None:
 34 |         msg = self.format(record)
 35 |         self._log_queue.put_nowait(msg)
 36 | 
 37 |     def _process_queue(self) -> None:
 38 |         while not self._stop_event.is_set():
 39 |             try:
 40 |                 msg = self._log_queue.get(timeout=1)
 41 |                 self._log_buffer.append(msg)
 42 |                 for callback in self._emit_callbacks:
 43 |                     try:
 44 |                         callback(msg)
 45 |                     except:
 46 |                         pass
 47 |                 self._log_queue.task_done()
 48 |             except queue.Empty:
 49 |                 continue
 50 | 
 51 |     def get_log_messages(self) -> list[str]:
 52 |         return self._log_buffer.get_log_messages()
 53 | 
 54 | 
 55 | class LogBuffer:
 56 |     """
 57 |     A thread-safe buffer for storing log messages.
 58 |     """
 59 | 
 60 |     def __init__(self) -> None:
 61 |         self._log_messages: list[str] = []
 62 |         self._lock = threading.Lock()
 63 | 
 64 |     def append(self, msg: str) -> None:
 65 |         with self._lock:
 66 |             self._log_messages.append(msg)
 67 | 
 68 |     def get_log_messages(self) -> list[str]:
 69 |         with self._lock:
 70 |             return self._log_messages.copy()
 71 | 
 72 | 
 73 | class SuspendedLoggersContext:
 74 |     """A context manager that provides an isolated logging environment.
 75 | 
 76 |     Temporarily removes all root log handlers upon entry, providing a clean slate
 77 |     for defining new log handlers within the context. Upon exit, restores the original
 78 |     logging configuration. This is useful when you need to temporarily configure
 79 |     an isolated logging setup with well-defined log handlers.
 80 | 
 81 |     The context manager:
 82 |         - Removes all existing (root) log handlers on entry
 83 |         - Allows defining new temporary handlers within the context
 84 |         - Restores the original configuration (handlers and root log level) on exit
 85 | 
 86 |     Example:
 87 |         >>> with SuspendedLoggersContext():
 88 |         ...     # No handlers are active here (configure your own and set desired log level)
 89 |         ...     pass
 90 |         >>> # Original log handlers are restored here
 91 | 
 92 |     """
 93 | 
 94 |     def __init__(self) -> None:
 95 |         self.saved_root_handlers: list = []
 96 |         self.saved_root_level: Optional[int] = None
 97 | 
 98 |     def __enter__(self) -> "SuspendedLoggersContext":
 99 |         root_logger = lg.getLogger()
100 |         self.saved_root_handlers = root_logger.handlers.copy()
101 |         self.saved_root_level = root_logger.level
102 |         root_logger.handlers.clear()
103 |         return self
104 | 
105 |     def __exit__(self, exc_type, exc_val, exc_tb) -> None:  # type: ignore
106 |         root_logger = lg.getLogger()
107 |         root_logger.handlers = self.saved_root_handlers
108 |         if self.saved_root_level is not None:
109 |             root_logger.setLevel(self.saved_root_level)
110 | 
```

--------------------------------------------------------------------------------
/roadmap.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Roadmap
 2 | 
 3 | This document gives an overview of the ongoing and future development of Serena.
 4 | If you have a proposal or want to discuss something, feel free to open a discussion
 5 | on Github. For a summary of the past development, see the [changelog](/CHANGELOG.md).
 6 | 
 7 | Want to see us reach our goals faster? You can help out with an issue, start a discussion, or 
 8 | inform us about funding opportunities so that we can devote more time to the project.
 9 | 
10 | ## Overall Goals
11 | 
12 | Serena has the potential to be the go-to tool for most LLM coding tasks, since it is 
13 | unique in its ability to be used as MCP Server in any kind of environment
14 | while still being a capable agent. We want to achieve the following goals in terms of functionality:
15 | 
16 | 1. Top performance (comparable to API-based coding agents) when used through official (free) clients like Claude Desktop.
17 | 1. Lowering API costs and potentially improving performance of coding clients (Claude Code, Codex, Cline, Roo, Cursor/Windsurf/VSCode etc).
18 | 1. Transparency and simplicity of use. Achieved through the dashboard/logging GUI.
19 | 1. Integrations with major frameworks that don't accept MCP. Usable as a library.
20 | 
21 | Apart from the functional goals, we have the goal of having great code design, so that Serena can be viewed
22 | as a reference for how to implement MCP Servers. Such projects are an emerging technology, and
23 | best practices are yet to be determined. We will share our experiences in [lessons learned](/lessons_learned.md).
24 | 
25 | 
26 | ## Immediate/Ongoing
27 | 
28 | - Support for projects using multiple programming languages.
29 | - Evaluate whether `ReplaceLinesTool` can be removed in favor of a more reliable and performant editing approach.
30 | - Generally experiment with various approaches to editing tools
31 | - Manual evaluation on selected tasks from SWE-verified
32 | - Manual evaluation of cost-lowering and performance when used within popular non-MCP agents
33 | - Improvements in prompts, in particular giving examples and extending modes and contexts
34 | 
35 | ## Upcoming
36 | 
37 | - Publishing Serena as a package that can also be used as library
38 | - Use linting and type-hierarchy from the LSP in tools
39 | - Tools for refactoring (rename, move) - speculative, maybe won't do this.
40 | - Tracking edits and rolling them back with the dashboard
41 | - Improve configurability and safety of shell tool. Maybe autogeneration of tools from a list of commands and descriptions.
42 | - Transparent comparison with DesktopCommander and ...
43 | - Automatic evaluation using OpenHands, submission to SWE-Bench
44 | - Evaluation whether incorporating other MCPs increases performance or usability (memory bank is a candidate)
45 | - More documentation and best practices
46 | 
47 | ## Stretch
48 | 
49 | - Allow for sandboxing and parallel instances of Serena, maybe use openhands or codex for that
50 | - Incorporate a verifier model or generally a second model (maybe for applying edits) as a tool.
51 | - Building on the above, allow for the second model itself to be reachable through an MCP server, so it can be used for free
52 | - Tracking edits performed with shell tools
53 | 
54 | ## Beyond Serena
55 | 
56 | The technologies and approaches taken in Serena can be used for various research and service ideas. Some thought that we had are:
57 | 
58 | - PR and issue assistant working with GitHub, similar to how [OpenHands](https://github.com/All-Hands-AI/OpenHands) 
59 |   and [qodo](https://github.com/qodo-ai/pr-agent) operate. Should be callable through @serena
60 | - Tuning a coding LLM with Serena's tools with RL on one-shot tasks. We would need compute-funding for that
61 | - Develop a web app to quantitatively compare the performance of various agents by scraping PRs and manually crafted metadata.
62 |   The main metric for coding agents should be *developer experience*, and that is hard to grasp and is poorly correlated with
63 |   performance on current benchmarks.
```
Page 2/21FirstPrevNextLast