#
tokens: 49922/50000 31/410 files (page 3/17)
lines: off (toggle) GitHub
raw markdown copy
This is page 3 of 17. Use http://codebase.md/oraios/serena?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/vue/test_repo/src/App.vue:
--------------------------------------------------------------------------------

```vue
<script setup lang="ts">
import { ref, computed, watch, watchEffect, onMounted } from 'vue'
import { useCalculatorStore } from '@/stores/calculator'
import { useThemeProvider } from '@/composables/useTheme'
import { useTimeFormatter } from '@/composables/useFormatter'
import CalculatorInput from '@/components/CalculatorInput.vue'
import CalculatorDisplay from '@/components/CalculatorDisplay.vue'

// Get the calculator store
const store = useCalculatorStore()

// Use theme composable with provide/inject - provides theme to all child components
const themeManager = useThemeProvider()

// Use time formatter composable
const timeFormatter = useTimeFormatter()

// Local ref for app title
const appTitle = ref('Vue Calculator')

// Computed property for app version
const appVersion = computed((): string => {
  return '1.0.0'
})

// Computed property for greeting message
const greetingMessage = computed((): string => {
  const hour = new Date().getHours()
  if (hour < 12) return 'Good morning'
  if (hour < 18) return 'Good afternoon'
  return 'Good evening'
})

// Get statistics from store
const totalCalculations = computed((): number => {
  return store.history.length
})

// Check if calculator is active
const isCalculatorActive = computed((): boolean => {
  return store.currentValue !== 0 || store.previousValue !== null
})

// Get last calculation time
const lastCalculationTime = computed((): string => {
  if (store.history.length === 0) return 'No calculations yet'
  const lastEntry = store.history[store.history.length - 1]
  return timeFormatter.getRelativeTime(lastEntry.timestamp)
})

// Watch for calculation count changes - demonstrates watchEffect
watchEffect(() => {
  document.title = `Calculator (${totalCalculations.value} calculations)`
})

// Watch for theme changes - demonstrates watch
watch(
  () => themeManager.isDarkMode.value,
  (isDark) => {
    console.log(`Theme changed to ${isDark ? 'dark' : 'light'} mode`)
  }
)

// Lifecycle hook
onMounted(() => {
  console.log('App component mounted')
  console.log(`Initial calculations: ${totalCalculations.value}`)
})

// Toggle theme method
const toggleTheme = () => {
  themeManager.toggleDarkMode()
}

<template>
  <div :class="['app', { 'dark-mode': themeManager.isDarkMode }]">
    <header class="app-header">
      <h1>{{ appTitle }}</h1>
      <div class="header-info">
        <span class="greeting">{{ greetingMessage }}!</span>
        <span class="version">v{{ appVersion }}</span>
        <button @click="toggleTheme" class="btn-theme">
          {{ themeManager.isDarkMode ? '☀️' : '🌙' }}
        </button>
      </div>
    </header>

    <main class="app-main">
      <div class="calculator-container">
        <div class="stats-bar">
          <span>Total Calculations: {{ totalCalculations }}</span>
          <span :class="{ 'active-indicator': isCalculatorActive }">
            {{ isCalculatorActive ? 'Active' : 'Idle' }}
          </span>
          <span class="last-calc-time">{{ lastCalculationTime }}</span>
        </div>

        <div class="calculator-grid">
          <CalculatorInput />
          <CalculatorDisplay />
        </div>
      </div>
    </main>

    <footer class="app-footer">
      <p>Built with Vue 3 + Pinia + TypeScript</p>
    </footer>
  </div>
</template>

<style scoped>
.app {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  transition: background 0.3s ease;
}

.app.dark-mode {
  background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
}

.app-header {
  padding: 2rem;
  color: white;
  text-align: center;
}

.app-header h1 {
  margin: 0 0 1rem 0;
  font-size: 2.5rem;
}

.header-info {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 1rem;
}

.greeting {
  font-size: 1.1rem;
}

.version {
  font-size: 0.9rem;
  opacity: 0.8;
}

.btn-theme {
  background: rgba(255, 255, 255, 0.2);
  border: none;
  border-radius: 50%;
  width: 40px;
  height: 40px;
  font-size: 1.2rem;
  cursor: pointer;
  transition: background 0.2s;
}

.btn-theme:hover {
  background: rgba(255, 255, 255, 0.3);
}

.app-main {
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 2rem;
}

.calculator-container {
  width: 100%;
  max-width: 1200px;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.stats-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 1rem;
  background: rgba(255, 255, 255, 0.9);
  border-radius: 8px;
  font-weight: 500;
}

.last-calc-time {
  font-size: 0.9rem;
  color: #666;
  font-style: italic;
}

.active-indicator {
  color: #4caf50;
  font-weight: bold;
}

.active-indicator:not(.active-indicator) {
  color: #999;
}

.calculator-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2rem;
}

@media (max-width: 768px) {
  .calculator-grid {
    grid-template-columns: 1fr;
  }
}

.app-footer {
  padding: 1rem;
  text-align: center;
  color: white;
  opacity: 0.8;
}

.app-footer p {
  margin: 0;
}
</style>

```

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

```python
"""
Tools supporting the general workflow of the agent
"""

import json
import platform

from serena.tools import Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional


class CheckOnboardingPerformedTool(Tool):
    """
    Checks whether project onboarding was already performed.
    """

    def apply(self) -> str:
        """
        Checks whether project onboarding was already performed.
        You should always call this tool before beginning to actually work on the project/after activating a project.
        """
        from .memory_tools import ListMemoriesTool

        list_memories_tool = self.agent.get_tool(ListMemoriesTool)
        memories = json.loads(list_memories_tool.apply())
        if len(memories) == 0:
            return (
                "Onboarding not performed yet (no memories available). "
                + "You should perform onboarding by calling the `onboarding` tool before proceeding with the task."
            )
        else:
            return f"""The onboarding was already performed, below is the list of available memories.
            Do not read them immediately, just remember that they exist and that you can read them later, if it is necessary
            for the current task.
            Some memories may be based on previous conversations, others may be general for the current project.
            You should be able to tell which one you need based on the name of the memory.
            
            {memories}"""


class OnboardingTool(Tool):
    """
    Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
    """

    def apply(self) -> str:
        """
        Call this tool if onboarding was not performed yet.
        You will call this tool at most once per conversation.

        :return: instructions on how to create the onboarding information
        """
        system = platform.system()
        return self.prompt_factory.create_onboarding_prompt(system=system)


class ThinkAboutCollectedInformationTool(Tool):
    """
    Thinking tool for pondering the completeness of collected information.
    """

    def apply(self) -> str:
        """
        Think about the collected information and whether it is sufficient and relevant.
        This tool should ALWAYS be called after you have completed a non-trivial sequence of searching steps like
        find_symbol, find_referencing_symbols, search_files_for_pattern, read_file, etc.
        """
        return self.prompt_factory.create_think_about_collected_information()


class ThinkAboutTaskAdherenceTool(Tool):
    """
    Thinking tool for determining whether the agent is still on track with the current task.
    """

    def apply(self) -> str:
        """
        Think about the task at hand and whether you are still on track.
        Especially important if the conversation has been going on for a while and there
        has been a lot of back and forth.

        This tool should ALWAYS be called before you insert, replace, or delete code.
        """
        return self.prompt_factory.create_think_about_task_adherence()


class ThinkAboutWhetherYouAreDoneTool(Tool):
    """
    Thinking tool for determining whether the task is truly completed.
    """

    def apply(self) -> str:
        """
        Whenever you feel that you are done with what the user has asked for, it is important to call this tool.
        """
        return self.prompt_factory.create_think_about_whether_you_are_done()


class SummarizeChangesTool(Tool, ToolMarkerOptional):
    """
    Provides instructions for summarizing the changes made to the codebase.
    """

    def apply(self) -> str:
        """
        Summarize the changes you have made to the codebase.
        This tool should always be called after you have fully completed any non-trivial coding task,
        but only after the think_about_whether_you_are_done call.
        """
        return self.prompt_factory.create_summarize_changes()


class PrepareForNewConversationTool(Tool):
    """
    Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
    """

    def apply(self) -> str:
        """
        Instructions for preparing for a new conversation. This tool should only be called on explicit user request.
        """
        return self.prompt_factory.create_prepare_for_new_conversation()


class InitialInstructionsTool(Tool, ToolMarkerDoesNotRequireActiveProject):
    """
    Provides instructions on how to use the Serena toolbox.
    Should only be used in settings where the system prompt is not read automatically by the client.

    NOTE: Some MCP clients (including Claude Desktop) do not read the system prompt automatically!
    """

    def apply(self) -> str:
        """
        Provides the 'Serena Instructions Manual', which contains essential information on how to use the Serena toolbox.
        IMPORTANT: If you have not yet read the manual, call this tool immediately after you are given your task by the user,
        as it will critically inform you!
        """
        return self.agent.create_system_prompt()

```

--------------------------------------------------------------------------------
/test/solidlsp/elixir/test_elixir_basic.py:
--------------------------------------------------------------------------------

```python
"""
Basic integration tests for the Elixir language server functionality.

These tests validate the functionality of the language server APIs
like request_references using the test repository.
"""

import os

import pytest

from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language

from . import EXPERT_UNAVAILABLE, EXPERT_UNAVAILABLE_REASON

# These marks will be applied to all tests in this module
pytestmark = [pytest.mark.elixir, pytest.mark.skipif(EXPERT_UNAVAILABLE, reason=f"Next LS not available: {EXPERT_UNAVAILABLE_REASON}")]


class TestElixirBasic:
    """Basic Elixir language server functionality tests."""

    @pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
    def test_request_references_function_definition(self, language_server: SolidLanguageServer):
        """Test finding references to a function definition."""
        file_path = os.path.join("lib", "models.ex")
        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()

        # Find the User module's 'new' function
        user_new_symbol = None
        for symbol in symbols[0]:  # Top level symbols
            if symbol.get("name") == "User" and symbol.get("kind") == 2:  # Module
                for child in symbol.get("children", []):
                    if child.get("name", "").startswith("def new(") and child.get("kind") == 12:  # Function
                        user_new_symbol = child
                        break
                break

        if not user_new_symbol or "selectionRange" not in user_new_symbol:
            pytest.skip("User.new function or its selectionRange not found")

        sel_start = user_new_symbol["selectionRange"]["start"]
        references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])

        assert references is not None
        assert len(references) > 0

        # Should find at least one reference (the definition itself)
        found_definition = any(ref["uri"].endswith("models.ex") for ref in references)
        assert found_definition, "Should find the function definition"

    @pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
    def test_request_references_create_user_function(self, language_server: SolidLanguageServer):
        """Test finding references to create_user function."""
        file_path = os.path.join("lib", "services.ex")
        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()

        # Find the UserService module's 'create_user' function
        create_user_symbol = None
        for symbol in symbols[0]:  # Top level symbols
            if symbol.get("name") == "UserService" and symbol.get("kind") == 2:  # Module
                for child in symbol.get("children", []):
                    if child.get("name", "").startswith("def create_user(") and child.get("kind") == 12:  # Function
                        create_user_symbol = child
                        break
                break

        if not create_user_symbol or "selectionRange" not in create_user_symbol:
            pytest.skip("UserService.create_user function or its selectionRange not found")

        sel_start = create_user_symbol["selectionRange"]["start"]
        references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])

        assert references is not None
        assert len(references) > 0

    @pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
    def test_request_referencing_symbols_function(self, language_server: SolidLanguageServer):
        """Test finding symbols that reference a specific function."""
        file_path = os.path.join("lib", "models.ex")
        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()

        # Find the User module's 'new' function
        user_new_symbol = None
        for symbol in symbols[0]:  # Top level symbols
            if symbol.get("name") == "User" and symbol.get("kind") == 2:  # Module
                for child in symbol.get("children", []):
                    if child.get("name", "").startswith("def new(") and child.get("kind") == 12:  # Function
                        user_new_symbol = child
                        break
                break

        if not user_new_symbol or "selectionRange" not in user_new_symbol:
            pytest.skip("User.new function or its selectionRange not found")

        sel_start = user_new_symbol["selectionRange"]["start"]
        referencing_symbols = language_server.request_referencing_symbols(file_path, sel_start["line"], sel_start["character"])

        assert referencing_symbols is not None

    @pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
    def test_timeout_enumeration_bug(self, language_server: SolidLanguageServer):
        """Test that enumeration doesn't timeout (regression test)."""
        # This should complete without timing out
        symbols = language_server.request_document_symbols("lib/models.ex").get_all_symbols_and_roots()
        assert symbols is not None

        # Test multiple symbol requests in succession
        for _ in range(3):
            symbols = language_server.request_document_symbols("lib/services.ex").get_all_symbols_and_roots()
            assert symbols is not None

```

--------------------------------------------------------------------------------
/test/serena/util/test_exception.py:
--------------------------------------------------------------------------------

```python
import os
from unittest.mock import MagicMock, Mock, patch

import pytest

from serena.util.exception import is_headless_environment, show_fatal_exception_safe


class TestHeadlessEnvironmentDetection:
    """Test class for headless environment detection functionality."""

    def test_is_headless_no_display(self):
        """Test that environment without DISPLAY is detected as headless on Linux."""
        with patch("sys.platform", "linux"):
            with patch.dict(os.environ, {}, clear=True):
                assert is_headless_environment() is True

    def test_is_headless_ssh_connection(self):
        """Test that SSH sessions are detected as headless."""
        with patch("sys.platform", "linux"):
            with patch.dict(os.environ, {"SSH_CONNECTION": "192.168.1.1 22 192.168.1.2 22", "DISPLAY": ":0"}):
                assert is_headless_environment() is True

            with patch.dict(os.environ, {"SSH_CLIENT": "192.168.1.1 22 22", "DISPLAY": ":0"}):
                assert is_headless_environment() is True

    def test_is_headless_wsl(self):
        """Test that WSL environment is detected as headless."""
        # Skip this test on Windows since os.uname doesn't exist
        if not hasattr(os, "uname"):
            pytest.skip("os.uname not available on this platform")

        with patch("sys.platform", "linux"):
            with patch("os.uname") as mock_uname:
                mock_uname.return_value = Mock(release="5.15.153.1-microsoft-standard-WSL2")
                with patch.dict(os.environ, {"DISPLAY": ":0"}):
                    assert is_headless_environment() is True

    def test_is_headless_docker(self):
        """Test that Docker containers are detected as headless."""
        with patch("sys.platform", "linux"):
            # Test with CI environment variable
            with patch.dict(os.environ, {"CI": "true", "DISPLAY": ":0"}):
                assert is_headless_environment() is True

            # Test with CONTAINER environment variable
            with patch.dict(os.environ, {"CONTAINER": "docker", "DISPLAY": ":0"}):
                assert is_headless_environment() is True

            # Test with .dockerenv file
            with patch("os.path.exists") as mock_exists:
                mock_exists.return_value = True
                with patch.dict(os.environ, {"DISPLAY": ":0"}):
                    assert is_headless_environment() is True

    def test_is_not_headless_windows(self):
        """Test that Windows is never detected as headless."""
        with patch("sys.platform", "win32"):
            # Even without DISPLAY, Windows should not be headless
            with patch.dict(os.environ, {}, clear=True):
                assert is_headless_environment() is False


class TestShowFatalExceptionSafe:
    """Test class for safe fatal exception display functionality."""

    @patch("serena.util.exception.is_headless_environment", return_value=True)
    @patch("serena.util.exception.log")
    def test_show_fatal_exception_safe_headless(self, mock_log, mock_is_headless):
        """Test that GUI is not attempted in headless environment."""
        test_exception = ValueError("Test error")

        # The import should never happen in headless mode
        with patch("serena.gui_log_viewer.show_fatal_exception") as mock_show_gui:
            show_fatal_exception_safe(test_exception)
            mock_show_gui.assert_not_called()

        # Verify debug log about skipping GUI
        mock_log.debug.assert_called_once_with("Skipping GUI error display in headless environment")

    @patch("serena.util.exception.is_headless_environment", return_value=False)
    @patch("serena.util.exception.log")
    def test_show_fatal_exception_safe_with_gui(self, mock_log, mock_is_headless):
        """Test that GUI is attempted when not in headless environment."""
        test_exception = ValueError("Test error")

        # Mock the GUI function
        with patch("serena.gui_log_viewer.show_fatal_exception") as mock_show_gui:
            show_fatal_exception_safe(test_exception)
            mock_show_gui.assert_called_once_with(test_exception)

    @patch("serena.util.exception.is_headless_environment", return_value=False)
    @patch("serena.util.exception.log")
    def test_show_fatal_exception_safe_gui_failure(self, mock_log, mock_is_headless):
        """Test graceful handling when GUI display fails."""
        test_exception = ValueError("Test error")
        gui_error = ImportError("No module named 'tkinter'")

        # Mock the GUI function to raise an exception
        with patch("serena.gui_log_viewer.show_fatal_exception", side_effect=gui_error):
            show_fatal_exception_safe(test_exception)

        # Verify debug log about GUI failure
        mock_log.debug.assert_called_with(f"Failed to show GUI error dialog: {gui_error}")

    def test_show_fatal_exception_safe_prints_to_stderr(self):
        """Test that exceptions are always printed to stderr."""
        test_exception = ValueError("Test error message")

        with patch("sys.stderr", new_callable=MagicMock) as mock_stderr:
            with patch("serena.util.exception.is_headless_environment", return_value=True):
                with patch("serena.util.exception.log"):
                    show_fatal_exception_safe(test_exception)

        # Verify print was called with the correct arguments
        mock_stderr.write.assert_any_call("Fatal exception: Test error message")

```

--------------------------------------------------------------------------------
/src/serena/resources/config/prompt_templates/simple_tool_outputs.yml:
--------------------------------------------------------------------------------

```yaml
# Some of Serena's tools are just outputting a fixed text block without doing anything else.
# Such tools are meant to encourage the agent to think in a certain way, to stay on track
# and so on. The (templates for) outputs of these tools are contained here.
prompts:
  onboarding_prompt: |
    You are viewing the project for the first time.
    Your task is to assemble relevant high-level information about the project which
    will be saved to memory files in the following steps.
    The information should be sufficient to understand what the project is about,
    and the most important commands for developing code.
    The project is being developed on the system: {{ system }}.

    You need to identify at least the following information:
    * the project's purpose
    * the tech stack used
    * the code style and conventions used (including naming, type hints, docstrings, etc.)
    * which commands to run when a task is completed (linting, formatting, testing, etc.)
    * the rough structure of the codebase
    * the commands for testing, formatting, and linting
    * the commands for running the entrypoints of the project
    * the util commands for the system, like `git`, `ls`, `cd`, `grep`, `find`, etc. Keep in mind that the system is {{ system }},
      so the commands might be different than on a regular unix system.
    * whether there are particular guidelines, styles, design patterns, etc. that one should know about

    This list is not exhaustive, you can add more information if you think it is relevant.

    For doing that, you will need to acquire information about the project with the corresponding tools.
    Read only the necessary files and directories to avoid loading too much data into memory.
    If you cannot find everything you need from the project itself, you should ask the user for more information.

    After collecting all the information, you will use the `write_memory` tool (in multiple calls) to save it to various memory files.
    A particularly important memory file will be the `suggested_commands.md` file, which should contain
    a list of commands that the user should know about to develop code in this project.
    Moreover, you should create memory files for the style and conventions and a dedicated memory file for
    what should be done when a task is completed.
    **Important**: after done with the onboarding task, remember to call the `write_memory` to save the collected information!

  think_about_collected_information: |
    Have you collected all the information you need for solving the current task? If not, can the missing information be acquired by using the available tools,
    in particular the tools related to symbol discovery? Or do you need to ask the user for more information?
    Think about it step by step and give a summary of the missing information and how it could be acquired.

  think_about_task_adherence: |
    Are you deviating from the task at hand? Do you need any additional information to proceed?
    Have you loaded all relevant memory files to see whether your implementation is fully aligned with the
    code style, conventions, and guidelines of the project? If not, adjust your implementation accordingly
    before modifying any code into the codebase.
    Note that it is better to stop and ask the user for clarification
    than to perform large changes which might not be aligned with the user's intentions.
    If you feel like the conversation is deviating too much from the original task, apologize and suggest to the user
    how to proceed. If the conversation became too long, create a summary of the current progress and suggest to the user
    to start a new conversation based on that summary.

  think_about_whether_you_are_done: |
    Have you already performed all the steps required by the task? Is it appropriate to run tests and linting, and if so,
    have you done that already? Is it appropriate to adjust non-code files like documentation and config and have you done that already?
    Should new tests be written to cover the changes?
    Note that a task that is just about exploring the codebase does not require running tests or linting.
    Read the corresponding memory files to see what should be done when a task is completed. 

  summarize_changes: |
    Summarize all the changes you have made to the codebase over the course of the conversation.
    Explore the diff if needed (e.g. by using `git diff`) to ensure that you have not missed anything.
    Explain whether and how the changes are covered by tests. Explain how to best use the new code, how to understand it,
    which existing code it affects and interacts with. Are there any dangers (like potential breaking changes or potential new problems) 
    that the user should be aware of? Should any new documentation be written or existing documentation updated?
    You can use tools to explore the codebase prior to writing the summary, but don't write any new code in this step until
    the summary is complete.

  prepare_for_new_conversation: |
    You have not yet completed the current task but we are running out of context.
    {mode_prepare_for_new_conversation}
    Imagine that you are handing over the task to another person who has access to the
    same tools and memory files as you do, but has not been part of the conversation so far.
    Write a summary that can be used in the next conversation to a memory file using the `write_memory` tool.

```

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

```python
"""
This file provides the implementation of the JSON-RPC client, that launches and
communicates with the language server.

The initial implementation of this file was obtained from
https://github.com/predragnikolic/OLSP under the MIT License with the following terms:

MIT License

Copyright (c) 2023 Предраг Николић

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

import dataclasses
import json
import logging
import os
from typing import Any, Union

from .lsp_types import ErrorCodes

StringDict = dict[str, Any]
PayloadLike = Union[list[StringDict], StringDict, None, bool]
CONTENT_LENGTH = "Content-Length: "
ENCODING = "utf-8"
log = logging.getLogger(__name__)


@dataclasses.dataclass
class ProcessLaunchInfo:
    """
    This class is used to store the information required to launch a (language server) process.
    """

    cmd: str | list[str]
    """
    the command used to launch the process.
    Specification as a list is preferred (as it is more robust and avoids incorrect quoting of arguments);
    the string variant is supported for backward compatibility only
    """

    env: dict[str, str] = dataclasses.field(default_factory=dict)
    """
    the environment variables to set for the process
    """

    cwd: str = os.getcwd()
    """
    the working directory for the process
    """


class LSPError(Exception):
    def __init__(self, code: ErrorCodes, message: str) -> None:
        super().__init__(message)
        self.code = code

    def to_lsp(self) -> StringDict:
        return {"code": self.code, "message": super().__str__()}

    @classmethod
    def from_lsp(cls, d: StringDict) -> "LSPError":
        return LSPError(d["code"], d["message"])

    def __str__(self) -> str:
        return f"{super().__str__()} ({self.code})"


def make_response(request_id: Any, params: PayloadLike) -> StringDict:
    return {"jsonrpc": "2.0", "id": request_id, "result": params}


def make_error_response(request_id: Any, err: LSPError) -> StringDict:
    return {"jsonrpc": "2.0", "id": request_id, "error": err.to_lsp()}


# LSP methods that expect NO params field at all (not even empty object).
# These methods use Void/unit type in their protocol definition.
# - shutdown: HLS uses Haskell's Void type, rust-analyzer expects unit
# - exit: Similar - notification with no params
# Sending params:{} to these methods causes parse errors like "Cannot parse Void"
# See: https://www.jsonrpc.org/specification ("params MAY be omitted")
_NO_PARAMS_METHODS = frozenset({"shutdown", "exit"})


def _build_params_field(method: str, params: PayloadLike) -> StringDict:
    """Build the params portion of a JSON-RPC message based on LSP method requirements.

    LSP methods with Void/unit type (shutdown, exit) must omit params field entirely
    to satisfy HLS and rust-analyzer. Other methods send empty {} for None params
    to maintain Delphi/FPC LSP compatibility (PR #851).

    Returns a dict that can be merged into the message using ** unpacking.
    """
    if method in _NO_PARAMS_METHODS:
        return {}  # Omit params entirely for Void-type methods
    elif params is not None:
        return {"params": params}
    else:
        return {"params": {}}  # Keep {} for Delphi/FPC compatibility


def make_notification(method: str, params: PayloadLike) -> StringDict:
    """Create a JSON-RPC 2.0 notification message."""
    return {"jsonrpc": "2.0", "method": method, **_build_params_field(method, params)}


def make_request(method: str, request_id: Any, params: PayloadLike) -> StringDict:
    """Create a JSON-RPC 2.0 request message."""
    return {"jsonrpc": "2.0", "method": method, "id": request_id, **_build_params_field(method, params)}


class StopLoopException(Exception):
    pass


def create_message(payload: PayloadLike) -> tuple[bytes, bytes, bytes]:
    body = json.dumps(payload, check_circular=False, ensure_ascii=False, separators=(",", ":")).encode(ENCODING)
    return (
        f"Content-Length: {len(body)}\r\n".encode(ENCODING),
        "Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n".encode(ENCODING),
        body,
    )


class MessageType:
    error = 1
    warning = 2
    info = 3
    log = 4


def content_length(line: bytes) -> int | None:
    if line.startswith(b"Content-Length: "):
        _, value = line.split(b"Content-Length: ")
        value = value.strip()
        try:
            return int(value)
        except ValueError:
            raise ValueError(f"Invalid Content-Length header: {value!r}")
    return None

```

--------------------------------------------------------------------------------
/src/solidlsp/language_servers/regal_server.py:
--------------------------------------------------------------------------------

```python
"""Regal Language Server implementation for Rego policy files."""

import logging
import os
import shutil
import threading

from overrides import override

from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_utils import PathUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings

log = logging.getLogger(__name__)


class RegalLanguageServer(SolidLanguageServer):
    """
    Provides Rego specific instantiation of the LanguageServer class using Regal.

    Regal is the official linter and language server for Rego (Open Policy Agent's policy language).
    See: https://github.com/StyraInc/regal
    """

    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        return super().is_ignored_dirname(dirname) or dirname in [".regal", ".opa"]

    def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
        """
        Creates a RegalLanguageServer instance.

        This class is not meant to be instantiated directly. Use LanguageServer.create() instead.

        :param config: Language server configuration
        :param repository_root_path: Path to the repository root
        :param solidlsp_settings: Settings for solidlsp
        """
        # Regal should be installed system-wide (via CI or user installation)
        regal_executable_path = shutil.which("regal")
        if not regal_executable_path:
            raise RuntimeError(
                "Regal language server not found. Please install it from https://github.com/StyraInc/regal or via your package manager."
            )

        super().__init__(
            config,
            repository_root_path,
            ProcessLaunchInfo(cmd=f"{regal_executable_path} language-server", cwd=repository_root_path),
            "rego",
            solidlsp_settings,
        )
        self.server_ready = threading.Event()

    @staticmethod
    def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
        """
        Returns the initialize params for the Regal Language Server.

        :param repository_absolute_path: Absolute path to the repository
        :return: LSP initialization parameters
        """
        root_uri = PathUtils.path_to_uri(repository_absolute_path)
        return {
            "processId": os.getpid(),
            "locale": "en",
            "rootPath": repository_absolute_path,
            "rootUri": root_uri,
            "capabilities": {
                "textDocument": {
                    "synchronization": {"didSave": True, "dynamicRegistration": True},
                    "completion": {"dynamicRegistration": True, "completionItem": {"snippetSupport": True}},
                    "definition": {"dynamicRegistration": True},
                    "references": {"dynamicRegistration": True},
                    "documentSymbol": {
                        "dynamicRegistration": True,
                        "hierarchicalDocumentSymbolSupport": True,
                        "symbolKind": {"valueSet": list(range(1, 27))},  # type: ignore[arg-type]
                    },
                    "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},  # type: ignore[list-item]
                    "codeAction": {"dynamicRegistration": True},
                    "formatting": {"dynamicRegistration": True},
                },
                "workspace": {
                    "workspaceFolders": True,
                    "didChangeConfiguration": {"dynamicRegistration": True},
                    "symbol": {"dynamicRegistration": True},
                },
            },
            "workspaceFolders": [
                {
                    "name": os.path.basename(repository_absolute_path),
                    "uri": root_uri,
                }
            ],
        }

    def _start_server(self) -> None:
        """Start Regal language server process and wait for initialization."""

        def register_capability_handler(params) -> None:  # type: ignore[no-untyped-def]
            return

        def window_log_message(msg) -> None:  # type: ignore[no-untyped-def]
            log.info(f"LSP: window/logMessage: {msg}")

        def do_nothing(params) -> None:  # type: ignore[no-untyped-def]
            return

        self.server.on_request("client/registerCapability", register_capability_handler)
        self.server.on_notification("window/logMessage", window_log_message)
        self.server.on_notification("$/progress", do_nothing)
        self.server.on_notification("textDocument/publishDiagnostics", do_nothing)

        log.info("Starting Regal language server process")
        self.server.start()
        initialize_params = self._get_initialize_params(self.repository_root_path)

        log.info(
            "Sending initialize request from LSP client to LSP server and awaiting response",
        )
        init_response = self.server.send.initialize(initialize_params)

        # Verify server capabilities
        assert "capabilities" in init_response
        assert "textDocumentSync" in init_response["capabilities"]

        self.server.notify.initialized({})
        self.completions_available.set()

        # Regal server is ready immediately after initialization
        self.server_ready.set()
        self.server_ready.wait()

```

--------------------------------------------------------------------------------
/docs/autogen_rst.py:
--------------------------------------------------------------------------------

```python
import logging
import os
import shutil
from pathlib import Path
from typing import Optional, List

log = logging.getLogger(os.path.basename(__file__))

TOP_LEVEL_PACKAGE = "serena"
PROJECT_NAME = "Serena"

def module_template(module_qualname: str):
    module_name = module_qualname.split(".")[-1]
    title = module_name.replace("_", r"\_")
    return f"""{title}
{"=" * len(title)}

.. automodule:: {module_qualname}
   :members:
   :undoc-members:
"""


def index_template(package_name: str, doc_references: Optional[List[str]] = None, text_prefix=""):
    doc_references = doc_references or ""
    if doc_references:
        doc_references = "\n" + "\n".join(f"* :doc:`{ref}`" for ref in doc_references) + "\n"

    dirname = package_name.split(".")[-1]
    title = dirname.replace("_", r"\_")
    if title == TOP_LEVEL_PACKAGE:
        title = "API Reference"
    return f"{title}\n{'=' * len(title)}" + text_prefix + doc_references


def write_to_file(content: str, path: str):
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "w") as f:
        f.write(content)
    os.chmod(path, 0o666)


_SUBTITLE = (
    f"\n Here is the autogenerated documentation of the {PROJECT_NAME} API. \n \n "
    "The Table of Contents to the left has the same structure as the "
    "repository's package code. The links at each page point to the submodules and subpackages. \n"
)


def make_rst(src_root, rst_root, clean=False, overwrite=False, package_prefix=""):
    """Creates/updates documentation in form of rst files for modules and packages.

    Does not delete any existing rst files. Thus, rst files for packages or modules that have been removed or renamed
    should be deleted by hand.

    This method should be executed from the project's top-level directory

    :param src_root: path to library base directory, typically "src/<library_name>"
    :param rst_root: path to the root directory to which .rst files will be written
    :param clean: whether to completely clean the target directory beforehand, removing any existing .rst files
    :param overwrite: whether to overwrite existing rst files. This should be used with caution as it will delete
        all manual changes to documentation files
    :package_prefix: a prefix to prepend to each module (for the case where the src_root is not the base package),
        which, if not empty, should end with a "."
    :return:
    """
    rst_root = os.path.abspath(rst_root)

    if clean and os.path.isdir(rst_root):
        shutil.rmtree(rst_root)

    base_package_name = package_prefix + os.path.basename(src_root)

    # TODO: reduce duplication with same logic for subpackages below
    files_in_dir = os.listdir(src_root)
    module_names = [f[:-3] for f in files_in_dir if f.endswith(".py") and not f.startswith("_")]
    subdir_refs = [
        f"{f}/index"
        for f in files_in_dir
        if os.path.isdir(os.path.join(src_root, f))
        and not f.startswith("_")
        and not f.startswith(".")
    ]
    package_index_rst_path = os.path.join(
        rst_root,
        "index.rst",
    )
    log.info(f"Writing {package_index_rst_path}")
    write_to_file(
        index_template(
            base_package_name,
            doc_references=module_names + subdir_refs,
            text_prefix=_SUBTITLE,
        ),
        package_index_rst_path,
    )

    for root, dirnames, filenames in os.walk(src_root):
        if os.path.basename(root).startswith("_"):
            continue
        base_package_relpath = os.path.relpath(root, start=src_root)
        base_package_qualname = package_prefix + os.path.relpath(
            root,
            start=os.path.dirname(src_root),
        ).replace(os.path.sep, ".")

        for dirname in dirnames:
            if dirname.startswith("_"):
                log.debug(f"Skipping {dirname}")
                continue
            files_in_dir = os.listdir(os.path.join(root, dirname))
            module_names = [
                f[:-3] for f in files_in_dir if f.endswith(".py") and not f.startswith("_")
            ]
            subdir_refs = [
                f"{f}/index"
                for f in files_in_dir
                if os.path.isdir(os.path.join(root, dirname, f)) and not f.startswith("_")
            ]
            package_qualname = f"{base_package_qualname}.{dirname}"
            package_index_rst_path = os.path.join(
                rst_root,
                base_package_relpath,
                dirname,
                "index.rst",
            )
            log.info(f"Writing {package_index_rst_path}")
            write_to_file(
                index_template(package_qualname, doc_references=module_names + subdir_refs),
                package_index_rst_path,
            )

        for filename in filenames:
            base_name, ext = os.path.splitext(filename)
            if ext == ".py" and not filename.startswith("_"):
                module_qualname = f"{base_package_qualname}.{filename[:-3]}"

                module_rst_path = os.path.join(rst_root, base_package_relpath, f"{base_name}.rst")
                if os.path.exists(module_rst_path) and not overwrite:
                    log.debug(f"{module_rst_path} already exists, skipping it")

                log.info(f"Writing module documentation to {module_rst_path}")
                write_to_file(module_template(module_qualname), module_rst_path)


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    docs_root = Path(__file__).parent
    enable_module_docs = False
    if enable_module_docs:
        make_rst(
            docs_root / ".." / "src" / "serena",
            docs_root / "serena",
            clean=True,
        )

```

--------------------------------------------------------------------------------
/test/solidlsp/matlab/test_matlab_basic.py:
--------------------------------------------------------------------------------

```python
"""
Basic integration tests for the MATLAB language server functionality.

These tests validate the functionality of the language server APIs
like request_document_symbols using the MATLAB test repository.

Requirements:
    - MATLAB R2021b or later must be installed
    - MATLAB_PATH environment variable should be set to MATLAB installation directory
    - Node.js and npm must be installed
"""

import os

import pytest

from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language

# Skip all tests if MATLAB is not available
pytestmark = pytest.mark.matlab

# Check if MATLAB is available
MATLAB_AVAILABLE = os.environ.get("MATLAB_PATH") is not None or any(
    os.path.exists(p)
    for p in [
        "/Applications/MATLAB_R2024b.app",
        "/Applications/MATLAB_R2025b.app",
        "/Volumes/S1/Applications/MATLAB_R2024b.app",
        "/Volumes/S1/Applications/MATLAB_R2025b.app",
    ]
)


@pytest.mark.skipif(not MATLAB_AVAILABLE, reason="MATLAB installation not found")
class TestMatlabLanguageServerBasics:
    """Test basic functionality of the MATLAB language server."""

    @pytest.mark.parametrize("language_server", [Language.MATLAB], indirect=True)
    def test_matlab_language_server_initialization(self, language_server: SolidLanguageServer) -> None:
        """Test that MATLAB language server can be initialized successfully."""
        assert language_server is not None
        assert language_server.language == Language.MATLAB

    @pytest.mark.parametrize("language_server", [Language.MATLAB], indirect=True)
    def test_matlab_request_document_symbols_class(self, language_server: SolidLanguageServer) -> None:
        """Test request_document_symbols for MATLAB class file."""
        # Test getting symbols from Calculator.m (class file)
        all_symbols, _root_symbols = language_server.request_document_symbols("Calculator.m").get_all_symbols_and_roots()

        # Extract class symbols (LSP Symbol Kind 5 for class)
        class_symbols = [symbol for symbol in all_symbols if symbol.get("kind") == 5]
        class_names = [symbol["name"] for symbol in class_symbols]

        # Should find the Calculator class
        assert "Calculator" in class_names, "Should find Calculator class"

        # Extract method symbols (LSP Symbol Kind 6 for method or 12 for function)
        method_symbols = [symbol for symbol in all_symbols if symbol.get("kind") in [6, 12]]
        method_names = [symbol["name"] for symbol in method_symbols]

        # Should find key methods
        expected_methods = ["add", "subtract", "multiply", "divide"]
        for method in expected_methods:
            assert method in method_names, f"Should find {method} method in Calculator class"

    @pytest.mark.parametrize("language_server", [Language.MATLAB], indirect=True)
    def test_matlab_request_document_symbols_function(self, language_server: SolidLanguageServer) -> None:
        """Test request_document_symbols for MATLAB function file."""
        # Test getting symbols from lib/mathUtils.m (function file)
        all_symbols, _root_symbols = language_server.request_document_symbols("lib/mathUtils.m").get_all_symbols_and_roots()

        # Extract function symbols (LSP Symbol Kind 12 for function)
        function_symbols = [symbol for symbol in all_symbols if symbol.get("kind") == 12]
        function_names = [symbol["name"] for symbol in function_symbols]

        # Should find the main mathUtils function
        assert "mathUtils" in function_names, "Should find mathUtils function"

        # Should also find nested/local functions
        expected_local_functions = ["computeFactorial", "computeFibonacci", "checkPrime", "computeStats"]
        for func in expected_local_functions:
            assert func in function_names, f"Should find {func} local function"

    @pytest.mark.parametrize("language_server", [Language.MATLAB], indirect=True)
    def test_matlab_request_document_symbols_script(self, language_server: SolidLanguageServer) -> None:
        """Test request_document_symbols for MATLAB script file."""
        # Test getting symbols from main.m (script file)
        all_symbols, _root_symbols = language_server.request_document_symbols("main.m").get_all_symbols_and_roots()

        # Scripts may have variables and sections, but less structured symbols
        # Just verify we can get symbols without errors
        assert all_symbols is not None


@pytest.mark.skipif(not MATLAB_AVAILABLE, reason="MATLAB installation not found")
class TestMatlabLanguageServerReferences:
    """Test find references functionality of the MATLAB language server."""

    @pytest.mark.parametrize("language_server", [Language.MATLAB], indirect=True)
    def test_matlab_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
        """Test finding references within a single MATLAB file."""
        # Find references to 'result' variable in Calculator.m
        # This is a basic test to verify references work
        references = language_server.request_references("Calculator.m", 25, 12)  # 'result' in add method

        # Should find at least the definition
        assert references is not None

    @pytest.mark.parametrize("language_server", [Language.MATLAB], indirect=True)
    def test_matlab_find_references_cross_file(self, language_server: SolidLanguageServer) -> None:
        """Test finding references across MATLAB files."""
        # Find references to Calculator class used in main.m
        references = language_server.request_references("main.m", 11, 8)  # 'Calculator' reference

        # Should find references in both main.m and Calculator.m
        assert references is not None

```

--------------------------------------------------------------------------------
/docs/01-about/020_programming-languages.md:
--------------------------------------------------------------------------------

```markdown
# Language Support

Serena provides a set of versatile code querying and editing functionalities
based on symbolic understanding of the code.
Equipped with these capabilities, Serena discovers and edits code just like a seasoned developer
making use of an IDE's capabilities would.
Serena can efficiently find the right context and do the right thing even in very large and
complex projects!

There are two alternative technologies powering these capabilities:

* **Language servers** implementing the language server Protocol (LSP) — the free/open-source alternative.
* **The Serena JetBrains Plugin**, which leverages the powerful code analysis and editing
  capabilities of your JetBrains IDE.

You can choose either of these backends depending on your preferences and requirements.

## Language Servers

Serena incorporates a powerful abstraction layer for the integration of language servers 
that implement the language server protocol (LSP).
It even supports multiple language servers in parallel to support polyglot projects.

The language servers themselves are typically open-source projects (like Serena)
or at least freely available for use.

We currently provide direct, out-of-the-box support for the programming languages listed below.
Some languages require additional installations or setup steps, as noted.

* **AL**
* **Bash**
* **C#**
* **C/C++**  
  (you may experience issues with finding references, we are working on it)
* **Clojure**
* **Dart**
* **Elixir**  
  (requires Elixir installation; Expert language server is downloaded automatically)
* **Elm**  
  (requires Elm compiler)
* **Erlang**  
  (requires installation of beam and [erlang_ls](https://github.com/erlang-ls/erlang_ls); experimental, might be slow or hang)
* **F#**  
  (requires .NET SDK 8.0+; uses FsAutoComplete/Ionide, which is auto-installed; for Homebrew .NET on macOS, set DOTNET_ROOT in your environment)
* **Fortran**   
  (requires installation of fortls: `pip install fortls`)
* **Go**  
  (requires installation of `gopls`)
* **Groovy**  
  (requires local groovy-language-server.jar setup via GROOVY_LS_JAR_PATH or configuration)
* **Haskell**  
  (automatically locates HLS via ghcup, stack, or system PATH; supports Stack and Cabal projects)
* **Java**  
* **JavaScript**
* **Julia**
* **Kotlin**  
  (uses the pre-alpha [official kotlin LS](https://github.com/Kotlin/kotlin-lsp), some issues may appear)
* **Lua**
* **Markdown**  
  (must be explicitly specified via `--language markdown` when generating project config, primarily useful for documentation-heavy projects)
* **Nix**  
  (requires nixd installation)
* **Pascal**
  (Free Pascal/Lazarus; automatically downloads pasls binary; set PP and FPCDIR environment variables for source navigation)
* **Perl**
  (requires installation of Perl::LanguageServer)
* **PHP**  
  (uses Intelephense LSP; set `INTELEPHENSE_LICENSE_KEY` environment variable for premium features)
* **Python**
* **R**  
  (requires installation of the `languageserver` R package)
* **Ruby**  
  (by default, uses [ruby-lsp](https://github.com/Shopify/ruby-lsp), specify ruby_solargraph as your language to use the previous solargraph based implementation)
* **Rust**  
  (requires [rustup](https://rustup.rs/) - uses rust-analyzer from your toolchain)
* **Scala**  
  (requires some [manual setup](../03-special-guides/scala_setup_guide_for_serena); uses Metals LSP)
* **Swift**
* **TypeScript**
* **Vue**    
  (3.x with TypeScript; requires Node.js v18+ and npm; supports .vue Single File Components with monorepo detection)
* **YAML**
* **Zig**  
  (requires installation of ZLS - Zig Language Server)

Support for further languages can easily be added by providing a shallow adapter for a new language server implementation,
see Serena's [memory on that](https://github.com/oraios/serena/blob/main/.serena/memories/adding_new_language_support_guide.md).

## The Serena JetBrains Plugin

As an alternative to language servers, the [Serena JetBrains Plugin](https://plugins.jetbrains.com/plugin/28946-serena/)
leverages the powerful code analysis capabilities of JetBrains IDEs. 
The plugin naturally supports all programming languages and frameworks that are supported by JetBrains IDEs, 
including IntelliJ IDEA, PyCharm, Android Studio, AppCode, WebStorm, PhpStorm, RubyMine, GoLand, AppCode, CLion, Rider, and others.

When using the plugin, Serena connects to an instance of your JetBrains IDE via the plugin. For users who already
work in a JetBrains IDE, this means Serena seamlessly integrates with the IDE instance you typically have open anyway,
requiring no additional setup or configuration beyond the plugin itself. This approach offers several key advantages:

* **External library indexing**: Dependencies and libraries are fully indexed and accessible to Serena
* **No additional setup**: No need to download or configure separate language servers
* **Enhanced performance**: Faster tool execution thanks to optimized IDE integration
* **Multi-language excellence**: First-class support for polyglot projects with multiple languages and frameworks

Even if you prefer to work in a different code editor, you can still benefit from the JetBrains plugin by running 
a JetBrains IDE instance (most have free community editions) alongside your preferred editor with your project 
opened and indexed. Serena will connect to the IDE for code analysis while you continue working in your editor 
of choice.

```{raw} html
<p>
<a href="https://plugins.jetbrains.com/plugin/28946-serena/">
<img style="background-color:transparent;" src="../_static/images/jetbrains-marketplace-button.png">
</a>
</p>
```

See the [JetBrains Plugin documentation](../02-usage/025_jetbrains_plugin) for usage details.
```

--------------------------------------------------------------------------------
/src/serena/resources/project.template.yml:
--------------------------------------------------------------------------------

```yaml
# list of languages for which language servers are started; choose from:
#   al               bash             clojure          cpp              csharp           csharp_omnisharp
#   dart             elixir           elm              erlang           fortran          fsharp
#   go               groovy           haskell          java             julia            kotlin
#   lua              markdown         nix              pascal           perl             php
#   powershell       python           python_jedi      r                rego             ruby
#   ruby_solargraph  rust             scala            swift            terraform        toml
#   typescript       typescript_vts   yaml             zig
# Note:
#   - For C, use cpp
#   - For JavaScript, use typescript
#   - For Free Pascal / Lazarus, use pascal
# Special requirements:
#   - csharp: Requires the presence of a .sln file in the project folder.
#   - pascal: Requires Free Pascal Compiler (fpc) and optionally Lazarus.
# When using multiple languages, the first language server that supports a given file will be used for that file.
# The first language is the default language and the respective language server will be used as a fallback.
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
languages: ["python"]

# the encoding used by text files in the project
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
encoding: "utf-8"

# whether to use the project's gitignore file to ignore files
# Added on 2025-04-07
ignore_all_files_in_gitignore: true

# list of additional paths to ignore
# same syntax as gitignore, so you can use * and **
# Was previously called `ignored_dirs`, please update your config if you are using that.
# Added (renamed) on 2025-04-07
ignored_paths: []

# whether the project is in read-only mode
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
# Added on 2025-04-18
read_only: false

# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
# Below is the complete list of tools for convenience.
# To make sure you have the latest list of tools, and to view their descriptions, 
# execute `uv run scripts/print_tool_overview.py`.
#
#  * `activate_project`: Activates a project by name.
#  * `check_onboarding_performed`: Checks whether project onboarding was already performed.
#  * `create_text_file`: Creates/overwrites a file in the project directory.
#  * `delete_lines`: Deletes a range of lines within a file.
#  * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
#  * `execute_shell_command`: Executes a shell command.
#  * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
#  * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
#  * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
#  * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
#  * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
#  * `initial_instructions`: Gets the initial instructions for the current project.
#     Should only be used in settings where the system prompt cannot be set,
#     e.g. in clients you have no control over, like Claude Desktop.
#  * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
#  * `insert_at_line`: Inserts content at a given line in a file.
#  * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
#  * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
#  * `list_memories`: Lists memories in Serena's project-specific memory store.
#  * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
#  * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
#  * `read_file`: Reads a file within the project directory.
#  * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
#  * `remove_project`: Removes a project from the Serena configuration.
#  * `replace_lines`: Replaces a range of lines within a file with new content.
#  * `replace_symbol_body`: Replaces the full definition of a symbol.
#  * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
#  * `search_for_pattern`: Performs a search for a pattern in the project.
#  * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
#  * `switch_modes`: Activates modes by providing a list of their names
#  * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
#  * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
#  * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
#  * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
excluded_tools: []

# initial prompt for the project. It will always be given to the LLM upon activating the project
# (contrary to the memories, which are loaded on demand).
initial_prompt: ""

project_name: "project_name"

```

--------------------------------------------------------------------------------
/DOCKER.md:
--------------------------------------------------------------------------------

```markdown
# Docker Setup for Serena (Experimental)

⚠️ **EXPERIMENTAL FEATURE**: The Docker setup for Serena is still experimental and has some limitations. Please read this entire document before using Docker with Serena.

## Overview

Docker support allows you to run Serena in an isolated container environment, which provides better security isolation for the shell tool and consistent dependencies across different systems.

## Benefits

- **Safer shell tool execution**: Commands run in an isolated container environment
- **Consistent dependencies**: No need to manage language servers and dependencies on your host system
- **Cross-platform support**: Works consistently across Windows, macOS, and Linux

## Important Usage Pointers

### Configuration

Serena's configuration and log files are stored in the container in `/workspaces/serena/config/`.
Any local configuration you may have for Serena will not apply; the container uses its own separate configuration.

You can mount a local configuration/data directory to persist settings across container restarts
(which will also contain session log files).
Simply mount your local directory to `/workspaces/serena/config` in the container.
Initially, be sure to add a `serena_config.yml` file to the mounted directory which applies the following
special settings for Docker usage:
```
# Disable the GUI log window since it's not supported in Docker
gui_log_window: False
# Listen on all interfaces for the web dashboard to be accessible from outside the container
web_dashboard_listen_address: 0.0.0.0
# Disable opening the web dashboard on launch (not possible within the container)
web_dashboard_open_on_launch: False
```
Set other configuration options as needed.

### Project Activation Limitations

- **Only mounted directories work**: Projects must be mounted as volumes to be accessible
- Projects outside the mounted directories cannot be activated or accessed
- Since projects are not remembered across container restarts (unless you mount a local configuration as described above), 
  activate them using the full path (e.g. `/workspaces/projects/my-project`) when using dynamic project activation

### Language Support Limitations

The default Docker image does not include dependencies for languages that
require explicit system-level installations.
Only languages that install their requirements on the fly will work out of the box.

### Dashboard Port Configuration

The web dashboard runs on port 24282 (0x5EDA) by default. You can configure this using environment variables:

```bash
# Use default ports
docker-compose up serena

# Use custom ports
SERENA_DASHBOARD_PORT=8080 docker-compose up serena
```

⚠️ **Note**: If the local port is occupied, you'll need to specify a different port using the environment variable.

### Line Ending Issues on Windows

⚠️ **Windows Users**: Be aware of potential line ending inconsistencies:
- Files edited within the Docker container may use Unix line endings (LF)
- Your Windows system may expect Windows line endings (CRLF)
- This can cause issues with version control and text editors
- Configure your Git settings appropriately: `git config core.autocrlf true`

## Quick Start

### Using Docker Compose (Recommended)

1. **Production mode** (for using Serena as MCP server):
   ```bash
   docker-compose up serena
   ```

2. **Development mode** (with source code mounted):
   ```bash
   docker-compose up serena-dev
   ```

Note: Edit the `compose.yaml` file to customize volume mounts for your projects.

### Building the Docker Image Manually

```bash
# Build the image
docker build -t serena .

# Run with current directory mounted
docker run -it --rm \
  -v "$(pwd)":/workspace \
  -p 9121:9121 \
  -p 24282:24282 \
  -e SERENA_DOCKER=1 \
  serena
```

### Using Docker Compose with Merge Compose files

To use Docker Compose with merge files, you can create a `compose.override.yml` file to customize the configuration:

```yaml
services:
  serena:
    # To work with projects, you must mount them as volumes:
    volumes:
      - ./my-project:/workspace/my-project
      - /path/to/another/project:/workspace/another-project
    # Add the context for the IDE assistant option:
    command:
      - "uv run --directory . serena-mcp-server --transport sse --port 9121 --host 0.0.0.0 --context claude-code"
```

See the [Docker Merge Compose files documentation](https://docs.docker.com/compose/how-tos/multiple-compose-files/merge/) for more details on using merge files.

## Accessing the Dashboard

Once running, access the web dashboard at:
- Default: http://localhost:24282/dashboard
- Custom port: http://localhost:${SERENA_DASHBOARD_PORT}/dashboard

## Volume Mounting

To work with projects, you must mount them as volumes:

```yaml
# In compose.yaml
volumes:
  - ./my-project:/workspace/my-project
  - /path/to/another/project:/workspace/another-project
```

## Environment Variables

- `SERENA_DOCKER=1`: Set automatically to indicate Docker environment
- `SERENA_PORT`: MCP server port (default: 9121)
- `SERENA_DASHBOARD_PORT`: Web dashboard port (default: 24282)
- `INTELEPHENSE_LICENSE_KEY`: License key for Intelephense PHP LSP premium features (optional)

## Troubleshooting

### Port Already in Use

If you see "port already in use" errors:
```bash
# Check what's using the port
lsof -i :24282  # macOS/Linux
netstat -ano | findstr :24282  # Windows

# Use a different port
SERENA_DASHBOARD_PORT=8080 docker-compose up serena
```

### Configuration Issues

If you need to reset Docker configuration:
```bash
# Remove Docker-specific config
rm serena_config.docker.yml

# Serena will auto-generate a new one on next run
```

### Project Access Issues

Ensure projects are properly mounted:
- Check volume mounts in `docker-compose.yaml`
- Use absolute paths for external projects
- Verify permissions on mounted directories

```

--------------------------------------------------------------------------------
/src/serena/agno.py:
--------------------------------------------------------------------------------

```python
import argparse
import logging
import os
import threading
from pathlib import Path
from typing import Any

from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.memory import MemoryManager
from agno.models.base import Model
from agno.tools.function import Function
from agno.tools.toolkit import Toolkit
from dotenv import load_dotenv
from sensai.util.logging import LogTime

from serena.agent import SerenaAgent, Tool
from serena.config.context_mode import SerenaAgentContext
from serena.constants import REPO_ROOT
from serena.util.exception import show_fatal_exception_safe

log = logging.getLogger(__name__)


class SerenaAgnoToolkit(Toolkit):
    def __init__(self, serena_agent: SerenaAgent):
        super().__init__("Serena")
        for tool in serena_agent.get_exposed_tool_instances():
            self.functions[tool.get_name_from_cls()] = self._create_agno_function(tool)
        log.info("Agno agent functions: %s", list(self.functions.keys()))

    @staticmethod
    def _create_agno_function(tool: Tool) -> Function:
        def entrypoint(**kwargs: Any) -> str:
            if "kwargs" in kwargs:
                # Agno sometimes passes a kwargs argument explicitly, so we merge it
                kwargs.update(kwargs["kwargs"])
                del kwargs["kwargs"]
            log.info(f"Calling tool {tool}")
            return tool.apply_ex(log_call=True, catch_exceptions=True, **kwargs)

        function = Function.from_callable(tool.get_apply_fn())
        function.name = tool.get_name_from_cls()
        function.entrypoint = entrypoint
        function.skip_entrypoint_processing = True
        return function


class SerenaAgnoAgentProvider:
    _agent: Agent | None = None
    _lock = threading.Lock()

    @classmethod
    def get_agent(cls, model: Model) -> Agent:
        """
        Returns the singleton instance of the Serena agent or creates it with the given parameters if it doesn't exist.

        NOTE: This is very ugly with poor separation of concerns, but the way in which the Agno UI works (reloading the
            module that defines the `app` variable) essentially forces us to do something like this.

        :param model: the large language model to use for the agent
        :return: the agent instance
        """
        with cls._lock:
            if cls._agent is not None:
                return cls._agent

            # change to Serena root
            os.chdir(REPO_ROOT)

            load_dotenv()

            parser = argparse.ArgumentParser(description="Serena coding assistant")

            # Create a mutually exclusive group
            group = parser.add_mutually_exclusive_group()

            # Add arguments to the group, both pointing to the same destination
            group.add_argument(
                "--project-file",
                required=False,
                help="Path to the project (or project.yml file).",
            )
            group.add_argument(
                "--project",
                required=False,
                help="Path to the project (or project.yml file).",
            )
            args = parser.parse_args()

            args_project_file = args.project or args.project_file

            if args_project_file:
                project_file = Path(args_project_file).resolve()
                # If project file path is relative, make it absolute by joining with project root
                if not project_file.is_absolute():
                    # Get the project root directory (parent of scripts directory)
                    project_root = Path(REPO_ROOT)
                    project_file = project_root / args_project_file

                # Ensure the path is normalized and absolute
                project_file = str(project_file.resolve())
            else:
                project_file = None

            with LogTime("Loading Serena agent"):
                try:
                    serena_agent = SerenaAgent(project_file, context=SerenaAgentContext.load("agent"))
                except Exception as e:
                    show_fatal_exception_safe(e)
                    raise

            # Even though we don't want to keep history between sessions,
            # for agno-ui to work as a conversation, we use a persistent database on disk.
            # This database should be deleted between sessions.
            # Note that this might collide with custom options for the agent, like adding vector-search based tools.
            sql_db_path = (Path("temp") / "agno_agent_storage.db").absolute()
            sql_db_path.parent.mkdir(exist_ok=True)
            # delete the db file if it exists
            log.info(f"Deleting DB from PID {os.getpid()}")
            if sql_db_path.exists():
                sql_db_path.unlink()

            agno_agent = Agent(
                name="Serena",
                model=model,
                # See explanation above on why database is needed
                db=SqliteDb(db_file=str(sql_db_path)),
                description="A fully-featured coding assistant",
                tools=[SerenaAgnoToolkit(serena_agent)],
                # Tool calls will be shown in the UI since that's configurable per tool
                # To see detailed logs, you should use the serena logger (configure it in the project file path)
                markdown=True,
                system_message=serena_agent.create_system_prompt(),
                telemetry=False,
                memory_manager=MemoryManager(),
                add_history_to_context=True,
                num_history_runs=100,  # you might want to adjust this (expense vs. history awareness)
            )
            cls._agent = agno_agent
            log.info(f"Agent instantiated: {agno_agent}")

        return agno_agent

```

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

```python
import os
from collections.abc import Iterator
from pathlib import Path

import pytest

from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils
from test.conftest import start_ls_context


@pytest.fixture(scope="class")
def rust_language_server() -> Iterator[SolidLanguageServer]:
    """Set up the test class with the Rust 2024 edition test repository."""
    test_repo_2024_path = TestRust2024EditionLanguageServer.test_repo_2024_path

    if not test_repo_2024_path.exists():
        pytest.skip("Rust 2024 edition test repository not found")

    # Create and start the language server for the 2024 edition repo
    with start_ls_context(Language.RUST, str(test_repo_2024_path)) as ls:
        yield ls


@pytest.mark.rust
class TestRust2024EditionLanguageServer:
    test_repo_2024_path = Path(__file__).parent.parent.parent / "resources" / "repos" / "rust" / "test_repo_2024"

    def test_find_references_raw(self, rust_language_server) -> None:
        # Test finding references to the 'add' function defined in main.rs
        file_path = os.path.join("src", "main.rs")
        symbols = rust_language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
        add_symbol = None
        for sym in symbols[0]:
            if sym.get("name") == "add":
                add_symbol = sym
                break
        assert add_symbol is not None, "Could not find 'add' function symbol in main.rs"
        sel_start = add_symbol["selectionRange"]["start"]
        refs = rust_language_server.request_references(file_path, sel_start["line"], sel_start["character"])
        # The add function should be referenced within main.rs itself (in the main function)
        assert any("main.rs" in ref.get("relativePath", "") for ref in refs), "main.rs should reference add function"

    def test_find_symbol(self, rust_language_server) -> None:
        symbols = rust_language_server.request_full_symbol_tree()
        assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main function not found in symbol tree"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add function not found in symbol tree"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "multiply"), "multiply function not found in symbol tree"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Calculator"), "Calculator struct not found in symbol tree"

    def test_find_referencing_symbols_multiply(self, rust_language_server) -> None:
        # Find references to 'multiply' function defined in lib.rs
        file_path = os.path.join("src", "lib.rs")
        symbols = rust_language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
        multiply_symbol = None
        for sym in symbols[0]:
            if sym.get("name") == "multiply":
                multiply_symbol = sym
                break
        assert multiply_symbol is not None, "Could not find 'multiply' function symbol in lib.rs"
        sel_start = multiply_symbol["selectionRange"]["start"]
        refs = rust_language_server.request_references(file_path, sel_start["line"], sel_start["character"])
        # The multiply function exists but may not be referenced anywhere, which is fine
        # This test just verifies we can find the symbol and request references without error
        assert isinstance(refs, list), "Should return a list of references (even if empty)"

    def test_find_calculator_struct_and_impl(self, rust_language_server) -> None:
        # Test finding the Calculator struct and its impl block
        file_path = os.path.join("src", "lib.rs")
        symbols = rust_language_server.request_document_symbols(file_path).get_all_symbols_and_roots()

        # Find the Calculator struct
        calculator_struct = None
        calculator_impl = None
        for sym in symbols[0]:
            if sym.get("name") == "Calculator" and sym.get("kind") == 23:  # Struct kind
                calculator_struct = sym
            elif sym.get("name") == "Calculator" and sym.get("kind") == 11:  # Interface/Impl kind
                calculator_impl = sym

        assert calculator_struct is not None, "Could not find 'Calculator' struct symbol in lib.rs"

        # The struct should have the 'result' field
        struct_children = calculator_struct.get("children", [])
        field_names = [child.get("name") for child in struct_children]
        assert "result" in field_names, "Calculator struct should have 'result' field"

        # Find the impl block and check its methods
        if calculator_impl is not None:
            impl_children = calculator_impl.get("children", [])
            method_names = [child.get("name") for child in impl_children]
            assert "new" in method_names, "Calculator impl should have 'new' method"
            assert "add" in method_names, "Calculator impl should have 'add' method"
            assert "get_result" in method_names, "Calculator impl should have 'get_result' method"

    def test_overview_methods(self, rust_language_server) -> None:
        symbols = rust_language_server.request_full_symbol_tree()
        assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main missing from overview"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add missing from overview"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "multiply"), "multiply missing from overview"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Calculator"), "Calculator missing from overview"

    def test_rust_2024_edition_specific(self) -> None:
        # Verify we're actually working with the 2024 edition repository
        cargo_toml_path = self.test_repo_2024_path / "Cargo.toml"
        assert cargo_toml_path.exists(), "Cargo.toml should exist in test repository"

        with open(cargo_toml_path) as f:
            content = f.read()
            assert 'edition = "2024"' in content, "Should be using Rust 2024 edition"

```

--------------------------------------------------------------------------------
/test/solidlsp/bash/test_bash_basic.py:
--------------------------------------------------------------------------------

```python
"""
Basic integration tests for the bash language server functionality.

These tests validate the functionality of the language server APIs
like request_document_symbols using the bash test repository.
"""

import pytest

from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language


@pytest.mark.bash
class TestBashLanguageServerBasics:
    """Test basic functionality of the bash language server."""

    @pytest.mark.parametrize("language_server", [Language.BASH], indirect=True)
    def test_bash_language_server_initialization(self, language_server: SolidLanguageServer) -> None:
        """Test that bash language server can be initialized successfully."""
        assert language_server is not None
        assert language_server.language == Language.BASH

    @pytest.mark.parametrize("language_server", [Language.BASH], indirect=True)
    def test_bash_request_document_symbols(self, language_server: SolidLanguageServer) -> None:
        """Test request_document_symbols for bash files."""
        # Test getting symbols from main.sh
        all_symbols, _root_symbols = language_server.request_document_symbols("main.sh").get_all_symbols_and_roots()

        # Extract function symbols (LSP Symbol Kind 12)
        function_symbols = [symbol for symbol in all_symbols if symbol.get("kind") == 12]
        function_names = [symbol["name"] for symbol in function_symbols]

        # Should detect all 3 functions from main.sh
        assert "greet_user" in function_names, "Should find greet_user function"
        assert "process_items" in function_names, "Should find process_items function"
        assert "main" in function_names, "Should find main function"
        assert len(function_symbols) >= 3, f"Should find at least 3 functions, found {len(function_symbols)}"

    @pytest.mark.parametrize("language_server", [Language.BASH], indirect=True)
    def test_bash_request_document_symbols_with_body(self, language_server: SolidLanguageServer) -> None:
        """Test request_document_symbols with body extraction."""
        # Test with include_body=True
        all_symbols, _root_symbols = language_server.request_document_symbols("main.sh").get_all_symbols_and_roots()

        function_symbols = [symbol for symbol in all_symbols if symbol.get("kind") == 12]

        # Find greet_user function and check it has body
        greet_user_symbol = next((sym for sym in function_symbols if sym["name"] == "greet_user"), None)
        assert greet_user_symbol is not None, "Should find greet_user function"

        if "body" in greet_user_symbol:
            body = greet_user_symbol["body"]
            assert "function greet_user()" in body, "Function body should contain function definition"
            assert "case" in body.lower(), "Function body should contain case statement"

    @pytest.mark.parametrize("language_server", [Language.BASH], indirect=True)
    def test_bash_utils_functions(self, language_server: SolidLanguageServer) -> None:
        """Test function detection in utils.sh file."""
        # Test with utils.sh as well
        utils_all_symbols, _utils_root_symbols = language_server.request_document_symbols("utils.sh").get_all_symbols_and_roots()

        utils_function_symbols = [symbol for symbol in utils_all_symbols if symbol.get("kind") == 12]
        utils_function_names = [symbol["name"] for symbol in utils_function_symbols]

        # Should detect functions from utils.sh
        expected_utils_functions = [
            "to_uppercase",
            "to_lowercase",
            "trim_whitespace",
            "backup_file",
            "contains_element",
            "log_message",
            "is_valid_email",
            "is_number",
        ]

        for func_name in expected_utils_functions:
            assert func_name in utils_function_names, f"Should find {func_name} function in utils.sh"

        assert len(utils_function_symbols) >= 8, f"Should find at least 8 functions in utils.sh, found {len(utils_function_symbols)}"

    @pytest.mark.parametrize("language_server", [Language.BASH], indirect=True)
    def test_bash_function_syntax_patterns(self, language_server: SolidLanguageServer) -> None:
        """Test that LSP detects different bash function syntax patterns correctly."""
        # Test main.sh (has both 'function' keyword and traditional syntax)
        main_all_symbols, _main_root_symbols = language_server.request_document_symbols("main.sh").get_all_symbols_and_roots()
        main_functions = [symbol for symbol in main_all_symbols if symbol.get("kind") == 12]
        main_function_names = [func["name"] for func in main_functions]

        # Test utils.sh (all use 'function' keyword)
        utils_all_symbols, _utils_root_symbols = language_server.request_document_symbols("utils.sh").get_all_symbols_and_roots()
        utils_functions = [symbol for symbol in utils_all_symbols if symbol.get("kind") == 12]
        utils_function_names = [func["name"] for func in utils_functions]

        # Verify LSP detects both syntax patterns
        # main() uses traditional syntax: main() {
        assert "main" in main_function_names, "LSP should detect traditional function syntax"

        # Functions with 'function' keyword: function name() {
        assert "greet_user" in main_function_names, "LSP should detect function keyword syntax"
        assert "process_items" in main_function_names, "LSP should detect function keyword syntax"

        # Verify all expected utils functions are detected by LSP
        expected_utils = [
            "to_uppercase",
            "to_lowercase",
            "trim_whitespace",
            "backup_file",
            "contains_element",
            "log_message",
            "is_valid_email",
            "is_number",
        ]

        for expected_func in expected_utils:
            assert expected_func in utils_function_names, f"LSP should detect {expected_func} function"

        # Verify total counts match expectations
        assert len(main_functions) >= 3, f"Should find at least 3 functions in main.sh, found {len(main_functions)}"
        assert len(utils_functions) >= 8, f"Should find at least 8 functions in utils.sh, found {len(utils_functions)}"

```

--------------------------------------------------------------------------------
/src/serena/analytics.py:
--------------------------------------------------------------------------------

```python
from __future__ import annotations

import logging
import threading
from abc import ABC, abstractmethod
from collections import defaultdict
from copy import copy
from dataclasses import asdict, dataclass
from enum import Enum

from anthropic.types import MessageParam, MessageTokensCount
from dotenv import load_dotenv

log = logging.getLogger(__name__)


class TokenCountEstimator(ABC):
    @abstractmethod
    def estimate_token_count(self, text: str) -> int:
        """
        Estimate the number of tokens in the given text.
        This is an abstract method that should be implemented by subclasses.
        """


class TiktokenCountEstimator(TokenCountEstimator):
    """
    Approximate token count using tiktoken.
    """

    def __init__(self, model_name: str = "gpt-4o"):
        """
        The tokenizer will be downloaded on the first initialization, which may take some time.

        :param model_name: see `tiktoken.model` to see available models.
        """
        import tiktoken

        log.info(f"Loading tiktoken encoding for model {model_name}, this may take a while on the first run.")
        self._encoding = tiktoken.encoding_for_model(model_name)

    def estimate_token_count(self, text: str) -> int:
        return len(self._encoding.encode(text))


class AnthropicTokenCount(TokenCountEstimator):
    """
    The exact count using the Anthropic API.
    Counting is free, but has a rate limit and will require an API key,
    (typically, set through an env variable).
    See https://docs.anthropic.com/en/docs/build-with-claude/token-counting
    """

    def __init__(self, model_name: str = "claude-sonnet-4-20250514", api_key: str | None = None):
        import anthropic

        self._model_name = model_name
        if api_key is None:
            load_dotenv()
        self._anthropic_client = anthropic.Anthropic(api_key=api_key)

    def _send_count_tokens_request(self, text: str) -> MessageTokensCount:
        return self._anthropic_client.messages.count_tokens(
            model=self._model_name,
            messages=[MessageParam(role="user", content=text)],
        )

    def estimate_token_count(self, text: str) -> int:
        return self._send_count_tokens_request(text).input_tokens


class CharCountEstimator(TokenCountEstimator):
    """
    A naive character count estimator that estimates tokens based on character count.
    """

    def __init__(self, avg_chars_per_token: int = 4):
        self._avg_chars_per_token = avg_chars_per_token

    def estimate_token_count(self, text: str) -> int:
        # Assuming an average of 4 characters per token
        return len(text) // self._avg_chars_per_token


_registered_token_estimator_instances_cache: dict[RegisteredTokenCountEstimator, TokenCountEstimator] = {}


class RegisteredTokenCountEstimator(Enum):
    TIKTOKEN_GPT4O = "TIKTOKEN_GPT4O"
    ANTHROPIC_CLAUDE_SONNET_4 = "ANTHROPIC_CLAUDE_SONNET_4"
    CHAR_COUNT = "CHAR_COUNT"

    @classmethod
    def get_valid_names(cls) -> list[str]:
        """
        Get a list of all registered token count estimator names.
        """
        return [estimator.name for estimator in cls]

    def _create_estimator(self) -> TokenCountEstimator:
        match self:
            case RegisteredTokenCountEstimator.TIKTOKEN_GPT4O:
                return TiktokenCountEstimator(model_name="gpt-4o")
            case RegisteredTokenCountEstimator.ANTHROPIC_CLAUDE_SONNET_4:
                return AnthropicTokenCount(model_name="claude-sonnet-4-20250514")
            case RegisteredTokenCountEstimator.CHAR_COUNT:
                return CharCountEstimator(avg_chars_per_token=4)
            case _:
                raise ValueError(f"Unknown token count estimator: {self}")

    def load_estimator(self) -> TokenCountEstimator:
        estimator_instance = _registered_token_estimator_instances_cache.get(self)
        if estimator_instance is None:
            estimator_instance = self._create_estimator()
            _registered_token_estimator_instances_cache[self] = estimator_instance
        return estimator_instance


class ToolUsageStats:
    """
    A class to record and manage tool usage statistics.
    """

    def __init__(self, token_count_estimator: RegisteredTokenCountEstimator = RegisteredTokenCountEstimator.TIKTOKEN_GPT4O):
        self._token_count_estimator = token_count_estimator.load_estimator()
        self._token_estimator_name = token_count_estimator.value
        self._tool_stats: dict[str, ToolUsageStats.Entry] = defaultdict(ToolUsageStats.Entry)
        self._tool_stats_lock = threading.Lock()

    @property
    def token_estimator_name(self) -> str:
        """
        Get the name of the registered token count estimator used.
        """
        return self._token_estimator_name

    @dataclass(kw_only=True)
    class Entry:
        num_times_called: int = 0
        input_tokens: int = 0
        output_tokens: int = 0

        def update_on_call(self, input_tokens: int, output_tokens: int) -> None:
            """
            Update the entry with the number of tokens used for a single call.
            """
            self.num_times_called += 1
            self.input_tokens += input_tokens
            self.output_tokens += output_tokens

    def _estimate_token_count(self, text: str) -> int:
        return self._token_count_estimator.estimate_token_count(text)

    def get_stats(self, tool_name: str) -> ToolUsageStats.Entry:
        """
        Get (a copy of) the current usage statistics for a specific tool.
        """
        with self._tool_stats_lock:
            return copy(self._tool_stats[tool_name])

    def record_tool_usage(self, tool_name: str, input_str: str, output_str: str) -> None:
        input_tokens = self._estimate_token_count(input_str)
        output_tokens = self._estimate_token_count(output_str)
        with self._tool_stats_lock:
            entry = self._tool_stats[tool_name]
            entry.update_on_call(input_tokens, output_tokens)

    def get_tool_stats_dict(self) -> dict[str, dict[str, int]]:
        with self._tool_stats_lock:
            return {name: asdict(entry) for name, entry in self._tool_stats.items()}

    def clear(self) -> None:
        with self._tool_stats_lock:
            self._tool_stats.clear()

```

--------------------------------------------------------------------------------
/test/solidlsp/elixir/conftest.py:
--------------------------------------------------------------------------------

```python
"""
Elixir-specific test configuration and fixtures.
"""

import os
import subprocess
import time
from pathlib import Path

import pytest


def ensure_elixir_test_repo_compiled(repo_path: str) -> None:
    """Ensure the Elixir test repository dependencies are installed and project is compiled.

    Next LS requires the project to be fully compiled and indexed before providing
    complete references and symbol resolution. This function:
    1. Installs dependencies via 'mix deps.get'
    2. Compiles the project via 'mix compile'

    This is essential in CI environments where dependencies aren't pre-installed.

    Args:
        repo_path: Path to the Elixir project root directory

    """
    # Check if this looks like an Elixir project
    mix_file = os.path.join(repo_path, "mix.exs")
    if not os.path.exists(mix_file):
        return

    # Check if already compiled (optimization for repeated runs)
    build_path = os.path.join(repo_path, "_build")
    deps_path = os.path.join(repo_path, "deps")

    if os.path.exists(build_path) and os.path.exists(deps_path):
        print(f"Elixir test repository already compiled in {repo_path}")
        return

    try:
        print("Installing dependencies and compiling Elixir test repository for optimal Next LS performance...")

        # First, install dependencies with increased timeout for CI
        print("=" * 60)
        print("Step 1/2: Installing Elixir dependencies...")
        print("=" * 60)
        start_time = time.time()

        deps_result = subprocess.run(
            ["mix", "deps.get"],
            cwd=repo_path,
            capture_output=True,
            text=True,
            timeout=180,
            check=False,  # 3 minutes for dependency installation (CI can be slow)
        )

        deps_duration = time.time() - start_time
        print(f"Dependencies installation completed in {deps_duration:.2f} seconds")

        # Always log the output for transparency
        if deps_result.stdout.strip():
            print("Dependencies stdout:")
            print("-" * 40)
            print(deps_result.stdout)
            print("-" * 40)

        if deps_result.stderr.strip():
            print("Dependencies stderr:")
            print("-" * 40)
            print(deps_result.stderr)
            print("-" * 40)

        if deps_result.returncode != 0:
            print(f"⚠️  Warning: Dependencies installation failed with exit code {deps_result.returncode}")
            # Continue anyway - some projects might not have dependencies
        else:
            print("✓ Dependencies installed successfully")

        # Then compile the project with increased timeout for CI
        print("=" * 60)
        print("Step 2/2: Compiling Elixir project...")
        print("=" * 60)
        start_time = time.time()

        compile_result = subprocess.run(
            ["mix", "compile"],
            cwd=repo_path,
            capture_output=True,
            text=True,
            timeout=300,
            check=False,  # 5 minutes for compilation (Credo compilation can be slow in CI)
        )

        compile_duration = time.time() - start_time
        print(f"Compilation completed in {compile_duration:.2f} seconds")

        # Always log the output for transparency
        if compile_result.stdout.strip():
            print("Compilation stdout:")
            print("-" * 40)
            print(compile_result.stdout)
            print("-" * 40)

        if compile_result.stderr.strip():
            print("Compilation stderr:")
            print("-" * 40)
            print(compile_result.stderr)
            print("-" * 40)

        if compile_result.returncode == 0:
            print(f"✓ Elixir test repository compiled successfully in {repo_path}")
        else:
            print(f"⚠️  Warning: Compilation completed with exit code {compile_result.returncode}")
            # Still continue - warnings are often non-fatal

        print("=" * 60)
        print(f"Total setup time: {time.time() - (start_time - compile_duration - deps_duration):.2f} seconds")
        print("=" * 60)

    except subprocess.TimeoutExpired as e:
        print("=" * 60)
        print(f"❌ TIMEOUT: Elixir setup timed out after {e.timeout} seconds")
        print(f"Command: {' '.join(e.cmd)}")
        print("This may indicate slow CI environment - Next LS may still work but with reduced functionality")

        # Try to get partial output if available
        if hasattr(e, "stdout") and e.stdout:
            print("Partial stdout before timeout:")
            print("-" * 40)
            print(e.stdout)
            print("-" * 40)
        if hasattr(e, "stderr") and e.stderr:
            print("Partial stderr before timeout:")
            print("-" * 40)
            print(e.stderr)
            print("-" * 40)
        print("=" * 60)

    except FileNotFoundError:
        print("❌ ERROR: 'mix' command not found - Elixir test repository may not be compiled")
        print("Please ensure Elixir is installed and available in PATH")
    except Exception as e:
        print(f"❌ ERROR: Failed to prepare Elixir test repository: {e}")


@pytest.fixture(scope="session", autouse=True)
def setup_elixir_test_environment():
    """Automatically prepare Elixir test environment for all Elixir tests.

    This fixture runs once per test session and automatically:
    1. Installs dependencies via 'mix deps.get'
    2. Compiles the Elixir test repository via 'mix compile'

    It uses autouse=True so it runs automatically without needing to be explicitly
    requested by tests. This ensures Next LS has a fully prepared project to work with.

    Uses generous timeouts (3-5 minutes) to accommodate slow CI environments.
    All output is logged for transparency and debugging.
    """
    # Get the test repo path relative to this conftest.py file
    test_repo_path = Path(__file__).parent.parent.parent / "resources" / "repos" / "elixir" / "test_repo"
    ensure_elixir_test_repo_compiled(str(test_repo_path))
    return str(test_repo_path)


@pytest.fixture(scope="session")
def elixir_test_repo_path(setup_elixir_test_environment):
    """Get the path to the prepared Elixir test repository.

    This fixture depends on setup_elixir_test_environment to ensure dependencies
    are installed and compilation has completed before returning the path.
    """
    return setup_elixir_test_environment

```

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

```python
"""
Erlang-specific test configuration and fixtures.
"""

import os
import subprocess
import time
from pathlib import Path

import pytest


def ensure_erlang_test_repo_compiled(repo_path: str) -> None:
    """Ensure the Erlang test repository dependencies are installed and project is compiled.

    Erlang LS requires the project to be fully compiled and indexed before providing
    complete references and symbol resolution. This function:
    1. Installs dependencies via 'rebar3 deps'
    2. Compiles the project via 'rebar3 compile'

    This is essential in CI environments where dependencies aren't pre-installed.

    Args:
        repo_path: Path to the Erlang project root directory

    """
    # Check if this looks like an Erlang project
    rebar_config = os.path.join(repo_path, "rebar.config")
    if not os.path.exists(rebar_config):
        return

    # Check if already compiled (optimization for repeated runs)
    build_path = os.path.join(repo_path, "_build")
    deps_path = os.path.join(repo_path, "deps")

    if os.path.exists(build_path) and os.path.exists(deps_path):
        print(f"Erlang test repository already compiled in {repo_path}")
        return

    try:
        print("Installing dependencies and compiling Erlang test repository for optimal Erlang LS performance...")

        # First, install dependencies with increased timeout for CI
        print("=" * 60)
        print("Step 1/2: Installing Erlang dependencies...")
        print("=" * 60)
        start_time = time.time()

        deps_result = subprocess.run(
            ["rebar3", "deps"],
            cwd=repo_path,
            capture_output=True,
            text=True,
            timeout=180,
            check=False,  # 3 minutes for dependency installation (CI can be slow)
        )

        deps_duration = time.time() - start_time
        print(f"Dependencies installation completed in {deps_duration:.2f} seconds")

        # Always log the output for transparency
        if deps_result.stdout.strip():
            print("Dependencies stdout:")
            print("-" * 40)
            print(deps_result.stdout)
            print("-" * 40)

        if deps_result.stderr.strip():
            print("Dependencies stderr:")
            print("-" * 40)
            print(deps_result.stderr)
            print("-" * 40)

        if deps_result.returncode != 0:
            print(f"⚠️  Warning: Dependencies installation failed with exit code {deps_result.returncode}")
            # Continue anyway - some projects might not have dependencies
        else:
            print("✓ Dependencies installed successfully")

        # Then compile the project with increased timeout for CI
        print("=" * 60)
        print("Step 2/2: Compiling Erlang project...")
        print("=" * 60)
        start_time = time.time()

        compile_result = subprocess.run(
            ["rebar3", "compile"],
            cwd=repo_path,
            capture_output=True,
            text=True,
            timeout=300,
            check=False,  # 5 minutes for compilation (Dialyzer can be slow in CI)
        )

        compile_duration = time.time() - start_time
        print(f"Compilation completed in {compile_duration:.2f} seconds")

        # Always log the output for transparency
        if compile_result.stdout.strip():
            print("Compilation stdout:")
            print("-" * 40)
            print(compile_result.stdout)
            print("-" * 40)

        if compile_result.stderr.strip():
            print("Compilation stderr:")
            print("-" * 40)
            print(compile_result.stderr)
            print("-" * 40)

        if compile_result.returncode == 0:
            print(f"✓ Erlang test repository compiled successfully in {repo_path}")
        else:
            print(f"⚠️  Warning: Compilation completed with exit code {compile_result.returncode}")
            # Still continue - warnings are often non-fatal

        print("=" * 60)
        print(f"Total setup time: {time.time() - (start_time - compile_duration - deps_duration):.2f} seconds")
        print("=" * 60)

    except subprocess.TimeoutExpired as e:
        print("=" * 60)
        print(f"❌ TIMEOUT: Erlang setup timed out after {e.timeout} seconds")
        print(f"Command: {' '.join(e.cmd)}")
        print("This may indicate slow CI environment - Erlang LS may still work but with reduced functionality")

        # Try to get partial output if available
        if hasattr(e, "stdout") and e.stdout:
            print("Partial stdout before timeout:")
            print("-" * 40)
            print(e.stdout)
            print("-" * 40)
        if hasattr(e, "stderr") and e.stderr:
            print("Partial stderr before timeout:")
            print("-" * 40)
            print(e.stderr)
            print("-" * 40)
        print("=" * 60)

    except FileNotFoundError:
        print("❌ ERROR: 'rebar3' command not found - Erlang test repository may not be compiled")
        print("Please ensure rebar3 is installed and available in PATH")
    except Exception as e:
        print(f"❌ ERROR: Failed to prepare Erlang test repository: {e}")


@pytest.fixture(scope="session", autouse=True)
def setup_erlang_test_environment():
    """Automatically prepare Erlang test environment for all Erlang tests.

    This fixture runs once per test session and automatically:
    1. Installs dependencies via 'rebar3 deps'
    2. Compiles the Erlang test repository via 'rebar3 compile'

    It uses autouse=True so it runs automatically without needing to be explicitly
    requested by tests. This ensures Erlang LS has a fully prepared project to work with.

    Uses generous timeouts (3-5 minutes) to accommodate slow CI environments.
    All output is logged for transparency and debugging.
    """
    # Get the test repo path relative to this conftest.py file
    test_repo_path = Path(__file__).parent.parent.parent / "resources" / "repos" / "erlang" / "test_repo"
    ensure_erlang_test_repo_compiled(str(test_repo_path))
    return str(test_repo_path)


@pytest.fixture(scope="session")
def erlang_test_repo_path(setup_erlang_test_environment):
    """Get the path to the prepared Erlang test repository.

    This fixture depends on setup_erlang_test_environment to ensure dependencies
    are installed and compilation has completed before returning the path.
    """
    return setup_erlang_test_environment

```

--------------------------------------------------------------------------------
/.serena/memories/serena_core_concepts_and_architecture.md:
--------------------------------------------------------------------------------

```markdown
# Serena Core Concepts and Architecture

## High-Level Architecture

Serena is built around a dual-layer architecture:

1. **SerenaAgent** - The main orchestrator that manages projects, tools, and user interactions
2. **SolidLanguageServer** - A unified wrapper around Language Server Protocol (LSP) implementations

## Core Components

### 1. SerenaAgent (`src/serena/agent.py`)

The central coordinator that:
- Manages active projects and their configurations
- Coordinates between different tools and contexts
- Handles language server lifecycle
- Manages memory persistence
- Provides MCP (Model Context Protocol) server interface

Key responsibilities:
- **Project Management** - Activating, switching between projects
- **Tool Registry** - Loading and managing available tools based on context/mode
- **Language Server Integration** - Starting/stopping language servers per project
- **Memory Management** - Persistent storage of project knowledge
- **Task Execution** - Coordinating complex multi-step operations

### 2. SolidLanguageServer (`src/solidlsp/ls.py`)

A unified abstraction over multiple language servers that provides:
- **Language-agnostic interface** for symbol operations
- **Caching layer** for performance optimization
- **Error handling and recovery** for unreliable language servers
- **Uniform API** regardless of underlying LSP implementation

Core capabilities:
- Symbol discovery and navigation
- Code completion and hover information
- Find references and definitions
- Document and workspace symbol search
- File watching and change notifications

### 3. Tool System (`src/serena/tools/`)

Modular tool architecture with several categories:

#### File Tools (`file_tools.py`)
- File system operations (read, write, list directories)
- Text search and pattern matching
- Regex-based replacements

#### Symbol Tools (`symbol_tools.py`)  
- Language-aware symbol finding and navigation
- Symbol body replacement and insertion
- Reference finding across codebase

#### Memory Tools (`memory_tools.py`)
- Project knowledge persistence
- Memory retrieval and management
- Onboarding information storage

#### Configuration Tools (`config_tools.py`)
- Project activation and switching
- Mode and context management
- Tool inclusion/exclusion

### 4. Configuration System (`src/serena/config/`)

Multi-layered configuration supporting:
- **Contexts** - Define available tools and their behavior
- **Modes** - Specify operational patterns (interactive, editing, etc.)
- **Projects** - Per-project settings and language server configs
- **Tool Sets** - Grouped tool collections for different use cases

## Language Server Integration

### Language Support Model

Each supported language has:
1. **Language Server Implementation** (`src/solidlsp/language_servers/`)
2. **Runtime Dependencies** - Managed downloads of language servers
3. **Test Repository** (`test/resources/repos/<language>/`)
4. **Test Suite** (`test/solidlsp/<language>/`)

### Language Server Lifecycle

1. **Discovery** - Find language servers or download them automatically
2. **Initialization** - Start server process and perform LSP handshake
3. **Project Setup** - Open workspace and configure language-specific settings
4. **Operation** - Handle requests/responses with caching and error recovery
5. **Shutdown** - Clean shutdown of server processes

### Supported Languages

Current language support includes:
- **C#** - Microsoft.CodeAnalysis.LanguageServer (.NET 9)
- **Python** - Pyright or Jedi
- **TypeScript/JavaScript** - TypeScript Language Server
- **Rust** - rust-analyzer
- **Go** - gopls
- **Java** - Eclipse JDT Language Server
- **Kotlin** - Kotlin Language Server
- **PHP** - Intelephense
- **Ruby** - Solargraph
- **Clojure** - clojure-lsp
- **Elixir** - ElixirLS
- **Dart** - Dart Language Server
- **C/C++** - clangd
- **Terraform** - terraform-ls

## Memory and Knowledge Management

### Memory System
- **Markdown-based storage** in `.serena/memories/` directory
- **Contextual retrieval** - memories loaded based on relevance
- **Project-specific** knowledge persistence
- **Onboarding support** - guided setup for new projects

### Knowledge Categories
- **Project Structure** - Directory layouts, build systems
- **Architecture Patterns** - How the codebase is organized
- **Development Workflows** - Testing, building, deployment
- **Domain Knowledge** - Business logic and requirements

## MCP Server Interface

Serena exposes its functionality through Model Context Protocol:
- **Tool Discovery** - AI agents can enumerate available tools
- **Context-Aware Operations** - Tools behave based on active project/mode
- **Stateful Sessions** - Maintains project state across interactions
- **Error Handling** - Graceful degradation when tools fail

## Error Handling and Resilience

### Language Server Reliability
- **Timeout Management** - Configurable timeouts for LSP requests
- **Process Recovery** - Automatic restart of crashed language servers
- **Fallback Behavior** - Graceful degradation when LSP unavailable
- **Caching Strategy** - Reduces impact of server failures

### Project Activation Safety
- **Validation** - Verify project structure before activation
- **Error Isolation** - Project failures don't affect other projects
- **Recovery Mechanisms** - Automatic cleanup and retry logic

## Performance Considerations

### Caching Strategy
- **Symbol Cache** - In-memory caching of expensive symbol operations
- **File System Cache** - Reduced disk I/O for repeated operations
- **Language Server Cache** - Persistent cache across sessions

### Resource Management
- **Language Server Pooling** - Reuse servers across projects when possible
- **Memory Management** - Automatic cleanup of unused resources
- **Background Operations** - Async operations don't block user interactions

## Extension Points

### Adding New Languages
1. Implement language server class in `src/solidlsp/language_servers/`
2. Add runtime dependencies configuration
3. Create test repository and test suite
4. Update language enumeration and configuration

### Adding New Tools
1. Inherit from `Tool` base class in `tools_base.py`
2. Implement required methods and parameter validation
3. Register tool in appropriate tool registry
4. Add to context/mode configurations as needed

### Custom Contexts and Modes
- Define new contexts in YAML configuration files
- Specify tool sets and operational patterns
- Configure for specific development workflows
```

--------------------------------------------------------------------------------
/src/solidlsp/language_servers/gopls.py:
--------------------------------------------------------------------------------

```python
import logging
import os
import pathlib
import subprocess
import threading
from typing import cast

from overrides import override

from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings

log = logging.getLogger(__name__)


class Gopls(SolidLanguageServer):
    """
    Provides Go specific instantiation of the LanguageServer class using gopls.
    """

    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        # For Go projects, we should ignore:
        # - vendor: third-party dependencies vendored into the project
        # - node_modules: if the project has JavaScript components
        # - dist/build: common output directories
        return super().is_ignored_dirname(dirname) or dirname in ["vendor", "node_modules", "dist", "build"]

    @staticmethod
    def _determine_log_level(line: str) -> int:
        """Classify gopls stderr output to avoid false-positive errors."""
        line_lower = line.lower()

        # File discovery messages that are not actual errors
        if any(
            [
                "discover.go:" in line_lower,
                "walker.go:" in line_lower,
                "walking of {file://" in line_lower,
                "bus: -> discover" in line_lower,
            ]
        ):
            return logging.DEBUG

        return SolidLanguageServer._determine_log_level(line)

    @staticmethod
    def _get_go_version() -> str | None:
        """Get the installed Go version or None if not found."""
        try:
            result = subprocess.run(["go", "version"], capture_output=True, text=True, check=False)
            if result.returncode == 0:
                return result.stdout.strip()
        except FileNotFoundError:
            return None
        return None

    @staticmethod
    def _get_gopls_version() -> str | None:
        """Get the installed gopls version or None if not found."""
        try:
            result = subprocess.run(["gopls", "version"], capture_output=True, text=True, check=False)
            if result.returncode == 0:
                return result.stdout.strip()
        except FileNotFoundError:
            return None
        return None

    @staticmethod
    def _setup_runtime_dependency() -> bool:
        """
        Check if required Go runtime dependencies are available.
        Raises RuntimeError with helpful message if dependencies are missing.
        """
        go_version = Gopls._get_go_version()
        if not go_version:
            raise RuntimeError(
                "Go is not installed. Please install Go from https://golang.org/doc/install and make sure it is added to your PATH."
            )

        gopls_version = Gopls._get_gopls_version()
        if not gopls_version:
            raise RuntimeError(
                "Found a Go version but gopls is not installed.\n"
                "Please install gopls as described in https://pkg.go.dev/golang.org/x/tools/gopls#section-readme\n\n"
                "After installation, make sure it is added to your PATH (it might be installed in a different location than Go)."
            )

        return True

    def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
        self._setup_runtime_dependency()

        super().__init__(config, repository_root_path, ProcessLaunchInfo(cmd="gopls", cwd=repository_root_path), "go", solidlsp_settings)
        self.server_ready = threading.Event()
        self.request_id = 0

    @staticmethod
    def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
        """
        Returns the initialize params for the Go Language Server.
        """
        root_uri = pathlib.Path(repository_absolute_path).as_uri()
        initialize_params = {
            "locale": "en",
            "capabilities": {
                "textDocument": {
                    "synchronization": {"didSave": True, "dynamicRegistration": True},
                    "definition": {"dynamicRegistration": True},
                    "documentSymbol": {
                        "dynamicRegistration": True,
                        "hierarchicalDocumentSymbolSupport": True,
                        "symbolKind": {"valueSet": list(range(1, 27))},
                    },
                },
                "workspace": {"workspaceFolders": True, "didChangeConfiguration": {"dynamicRegistration": True}},
            },
            "processId": os.getpid(),
            "rootPath": repository_absolute_path,
            "rootUri": root_uri,
            "workspaceFolders": [
                {
                    "uri": root_uri,
                    "name": os.path.basename(repository_absolute_path),
                }
            ],
        }
        return cast(InitializeParams, initialize_params)

    def _start_server(self) -> None:
        """Start gopls server process"""

        def register_capability_handler(params: dict) -> None:
            return

        def window_log_message(msg: dict) -> None:
            log.info(f"LSP: window/logMessage: {msg}")

        def do_nothing(params: dict) -> None:
            return

        self.server.on_request("client/registerCapability", register_capability_handler)
        self.server.on_notification("window/logMessage", window_log_message)
        self.server.on_notification("$/progress", do_nothing)
        self.server.on_notification("textDocument/publishDiagnostics", do_nothing)

        log.info("Starting gopls server process")
        self.server.start()
        initialize_params = self._get_initialize_params(self.repository_root_path)

        log.info("Sending initialize request from LSP client to LSP server and awaiting response")
        init_response = self.server.send.initialize(initialize_params)

        # Verify server capabilities
        assert "textDocumentSync" in init_response["capabilities"]
        assert "completionProvider" in init_response["capabilities"]
        assert "definitionProvider" in init_response["capabilities"]

        self.server.notify.initialized({})
        self.completions_available.set()

        # gopls server is typically ready immediately after initialization
        self.server_ready.set()
        self.server_ready.wait()

```

--------------------------------------------------------------------------------
/docs/02-usage/040_workflow.md:
--------------------------------------------------------------------------------

```markdown
# The Project Workflow

Serena uses a project-based workflow.
A **project** is simply a directory on your filesystem that contains code and other files
that you want Serena to work with.

Assuming that you have project you want to work with (which may initially be empty),
setting up a project with Serena typically involves the following steps:

1. **Project creation**: Configuring project settings for Serena (and indexing the project, if desired)
2. **Project activation**: Making Serena aware of the project you want to work with
3. **Onboarding**: Getting Serena familiar with the project (creating memories)
4. **Working on coding tasks**: Using Serena to help you with actual coding tasks in the project

(project-creation-indexing)=
## Project Creation & Indexing

You can create a project either  
 * implicitly, by just activating a directory as a project while already in a conversation; this will use default settings for your project (skip to the next section).
 * explicitly, using the project creation command, or

### Explicit Project Creation

To explicitly create a project, use the following command while in the project directory:

    <serena> project create [options]

For instance, when using `uvx`, run

    uvx --from git+https://github.com/oraios/serena serena project create [options]

 * For an empty project, you will need to specify the programming language
   (e.g., `--language python`). 
 * For an existing project, the main programming language will be detected automatically,
   but you can choose to explicitly specify multiple languages by passing the `--language` parameter
   multiple times (e.g. `--language python --language typescript`).
 * You can optionally specify a custom project name with `--name "My Project"`.
 * You can immediately index the project after creation with `--index`.

After creation, you can adjust the project settings in the generated `.serena/project.yml` file.

(indexing)=
### Indexing

Especially for larger project, it is advisable to index the project after creation (in order to avoid
delays during MCP server startup or the first tool application):

While in the project directory, run this command:
   
    <serena> project index

Indexing has to be called only once. During regular usage, Serena will automatically update the index whenever files change.

## Project Activation
   
Project activation makes Serena aware of the project you want to work with.
You can either choose to do this
 * while in a conversation, by telling the LLM to activate a project, e.g.,
       
      * "Activate the project /path/to/my_project" (for first-time activation with auto-creation)
      * "Activate the project my_project"
   
   Note that this option requires the `activate_project` tool to be active, 
   which it isn't in single-project [contexts](contexts) like `ide` or `claude-code` *if* a project is provided at startup.
   (The tool is deactivated, because we assume that in these contexts, user will only work on the single, open project and have
   no need to switch it.)

 * when the MCP server starts, by passing the project path or name as a command-line argument
   (e.g. when using a single-project mode like `ide` or `claude-code`): `--project <path|name>`


## Onboarding & Memories

By default, Serena will perform an **onboarding process** when
it is started for the first time for a project.
The goal of the onboarding is for Serena to get familiar with the project
and to store memories, which it can then draw upon in future interactions.
If an LLM should fail to complete the onboarding and does not actually write the
respective memories to disk, you may need to ask it to do so explicitly.

The onboarding will usually read a lot of content from the project, thus filling
up the context. It can therefore be advisable to switch to another conversation
once the onboarding is complete.
After the onboarding, we recommend that you have a quick look at the memories and,
if necessary, edit them or add additional ones.

**Memories** are files stored in `.serena/memories/` in the project directory,
which the agent can choose to read in subsequent interactions.
Feel free to read and adjust them as needed; you can also add new ones manually.
Every file in the `.serena/memories/` directory is a memory file.
Whenever Serena starts working on a project, the list of memories is
provided, and the agent can decide to read them.
We found that memories can significantly improve the user experience with Serena.


## Preparing Your Project

When using Serena to work on your project, it can be helpful to follow a few best practices.

### Structure Your Codebase

Serena uses the code structure for finding, reading and editing code. This means that it will
work well with well-structured code but may perform poorly on fully unstructured one (like a "God class"
with enormous, non-modular functions).

Furthermore, for languages that are not statically typed, the use of type annotations (if supported) 
are highly beneficial.

### Start from a Clean State

It is best to start a code generation task from a clean git state. Not only will
this make it easier for you to inspect the changes, but also the model itself will
have a chance of seeing what it has changed by calling `git diff` and thereby
correct itself or continue working in a followup conversation if needed.

### Use Platform-Native Line Endings

**Important**: since Serena will write to files using the system-native line endings
and it might want to look at the git diff, it is important to
set `git config core.autocrlf` to `true` on Windows.
With `git config core.autocrlf` set to `false` on Windows, you may end up with huge diffs
due to line endings only. 
It is generally a good idea to globally enable this git setting on Windows:

```shell
git config --global core.autocrlf true
```

### Logging, Linting, and Automated Tests

Serena can successfully complete tasks in an _agent loop_, where it iteratively
acquires information, performs actions, and reflects on the results.
However, Serena cannot use a debugger; it must rely on the results of program executions,
linting results, and test results to assess the correctness of its actions.
Therefore, software that is designed to meaningful interpretable outputs (e.g. log messages)
and that has a good test coverage is much easier to work with for Serena.

We generally recommend to start an editing task from a state where all linting checks and tests pass.
```

--------------------------------------------------------------------------------
/src/solidlsp/language_servers/dart_language_server.py:
--------------------------------------------------------------------------------

```python
import logging
import os
import pathlib
from typing import cast

from solidlsp.ls import SolidLanguageServer
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings

from ..ls_config import LanguageServerConfig
from ..lsp_protocol_handler.lsp_types import InitializeParams
from .common import RuntimeDependency, RuntimeDependencyCollection

log = logging.getLogger(__name__)


class DartLanguageServer(SolidLanguageServer):
    """
    Provides Dart specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Dart.
    """

    def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings) -> None:
        """
        Creates a DartServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
        """
        executable_path = self._setup_runtime_dependencies(solidlsp_settings)
        super().__init__(
            config, repository_root_path, ProcessLaunchInfo(cmd=executable_path, cwd=repository_root_path), "dart", solidlsp_settings
        )

    @classmethod
    def _setup_runtime_dependencies(cls, solidlsp_settings: SolidLSPSettings) -> str:
        deps = RuntimeDependencyCollection(
            [
                RuntimeDependency(
                    id="DartLanguageServer",
                    description="Dart Language Server for Linux (x64)",
                    url="https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-linux-x64-release.zip",
                    platform_id="linux-x64",
                    archive_type="zip",
                    binary_name="dart-sdk/bin/dart",
                ),
                RuntimeDependency(
                    id="DartLanguageServer",
                    description="Dart Language Server for Windows (x64)",
                    url="https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-windows-x64-release.zip",
                    platform_id="win-x64",
                    archive_type="zip",
                    binary_name="dart-sdk/bin/dart.exe",
                ),
                RuntimeDependency(
                    id="DartLanguageServer",
                    description="Dart Language Server for Windows (arm64)",
                    url="https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-windows-arm64-release.zip",
                    platform_id="win-arm64",
                    archive_type="zip",
                    binary_name="dart-sdk/bin/dart.exe",
                ),
                RuntimeDependency(
                    id="DartLanguageServer",
                    description="Dart Language Server for macOS (x64)",
                    url="https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-macos-x64-release.zip",
                    platform_id="osx-x64",
                    archive_type="zip",
                    binary_name="dart-sdk/bin/dart",
                ),
                RuntimeDependency(
                    id="DartLanguageServer",
                    description="Dart Language Server for macOS (arm64)",
                    url="https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-macos-arm64-release.zip",
                    platform_id="osx-arm64",
                    archive_type="zip",
                    binary_name="dart-sdk/bin/dart",
                ),
            ]
        )

        dart_ls_dir = cls.ls_resources_dir(solidlsp_settings)
        dart_executable_path = deps.binary_path(dart_ls_dir)

        if not os.path.exists(dart_executable_path):
            deps.install(dart_ls_dir)

        assert os.path.exists(dart_executable_path)
        os.chmod(dart_executable_path, 0o755)

        return f"{dart_executable_path} language-server --client-id multilspy.dart --client-version 1.2"

    @staticmethod
    def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
        """
        Returns the initialize params for the Dart Language Server.
        """
        root_uri = pathlib.Path(repository_absolute_path).as_uri()
        initialize_params = {
            "capabilities": {},
            "initializationOptions": {
                "onlyAnalyzeProjectsWithOpenFiles": False,
                "closingLabels": False,
                "outline": False,
                "flutterOutline": False,
                "allowOpenUri": False,
            },
            "trace": "verbose",
            "processId": os.getpid(),
            "rootPath": repository_absolute_path,
            "rootUri": pathlib.Path(repository_absolute_path).as_uri(),
            "workspaceFolders": [
                {
                    "uri": root_uri,
                    "name": os.path.basename(repository_absolute_path),
                }
            ],
        }

        return cast(InitializeParams, initialize_params)

    def _start_server(self) -> None:
        """
        Start the language server and yield when the server is ready.
        """

        def execute_client_command_handler(params: dict) -> list:
            return []

        def do_nothing(params: dict) -> None:
            return

        def check_experimental_status(params: dict) -> None:
            pass

        def window_log_message(msg: dict) -> None:
            log.info(f"LSP: window/logMessage: {msg}")

        self.server.on_request("client/registerCapability", do_nothing)
        self.server.on_notification("language/status", do_nothing)
        self.server.on_notification("window/logMessage", window_log_message)
        self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
        self.server.on_notification("$/progress", do_nothing)
        self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
        self.server.on_notification("language/actionableNotification", do_nothing)
        self.server.on_notification("experimental/serverStatus", check_experimental_status)

        log.info("Starting dart-language-server server process")
        self.server.start()
        initialize_params = self._get_initialize_params(self.repository_root_path)
        log.debug("Sending initialize request to dart-language-server")
        init_response = self.server.send_request("initialize", initialize_params)  # type: ignore
        log.info(f"Received initialize response from dart-language-server: {init_response}")

        self.server.notify.initialized({})

```

--------------------------------------------------------------------------------
/src/solidlsp/language_servers/r_language_server.py:
--------------------------------------------------------------------------------

```python
import logging
import os
import pathlib
import subprocess
import threading
from typing import Any

from overrides import override

from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings

log = logging.getLogger(__name__)


class RLanguageServer(SolidLanguageServer):
    """R Language Server implementation using the languageserver R package."""

    @override
    def _get_wait_time_for_cross_file_referencing(self) -> float:
        return 5.0  # R language server needs extra time for workspace indexing in CI environments

    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        # For R projects, ignore common directories
        return super().is_ignored_dirname(dirname) or dirname in [
            "renv",  # R environment management
            "packrat",  # Legacy R package management
            ".Rproj.user",  # RStudio project files
            "vignettes",  # Package vignettes (often large)
        ]

    @staticmethod
    def _check_r_installation() -> None:
        """Check if R and languageserver are available."""
        try:
            # Check R installation
            result = subprocess.run(["R", "--version"], capture_output=True, text=True, check=False)
            if result.returncode != 0:
                raise RuntimeError("R is not installed or not in PATH")

            # Check languageserver package
            result = subprocess.run(
                ["R", "--vanilla", "--quiet", "--slave", "-e", "if (!require('languageserver', quietly=TRUE)) quit(status=1)"],
                capture_output=True,
                text=True,
                check=False,
            )

            if result.returncode != 0:
                raise RuntimeError(
                    "R languageserver package is not installed.\nInstall it with: R -e \"install.packages('languageserver')\""
                )

        except FileNotFoundError:
            raise RuntimeError("R is not installed. Please install R from https://www.r-project.org/")

    def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
        # Check R installation
        self._check_r_installation()

        # R command to start language server
        # Use --vanilla for minimal startup and --quiet to suppress all output except LSP
        # Set specific options to improve parsing stability
        r_cmd = 'R --vanilla --quiet --slave -e "options(languageserver.debug_mode = FALSE); languageserver::run()"'

        super().__init__(config, repository_root_path, ProcessLaunchInfo(cmd=r_cmd, cwd=repository_root_path), "r", solidlsp_settings)
        self.server_ready = threading.Event()

    @staticmethod
    def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
        """Initialize params for R Language Server."""
        root_uri = pathlib.Path(repository_absolute_path).as_uri()
        initialize_params = {
            "locale": "en",
            "capabilities": {
                "textDocument": {
                    "synchronization": {"didSave": True, "dynamicRegistration": True},
                    "completion": {
                        "dynamicRegistration": True,
                        "completionItem": {
                            "snippetSupport": True,
                            "commitCharactersSupport": True,
                            "documentationFormat": ["markdown", "plaintext"],
                            "deprecatedSupport": True,
                            "preselectSupport": True,
                        },
                    },
                    "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
                    "definition": {"dynamicRegistration": True},
                    "references": {"dynamicRegistration": True},
                    "documentSymbol": {
                        "dynamicRegistration": True,
                        "hierarchicalDocumentSymbolSupport": True,
                        "symbolKind": {"valueSet": list(range(1, 27))},
                    },
                    "formatting": {"dynamicRegistration": True},
                    "rangeFormatting": {"dynamicRegistration": True},
                },
                "workspace": {
                    "workspaceFolders": True,
                    "didChangeConfiguration": {"dynamicRegistration": True},
                    "symbol": {
                        "dynamicRegistration": True,
                        "symbolKind": {"valueSet": list(range(1, 27))},
                    },
                },
            },
            "processId": os.getpid(),
            "rootPath": repository_absolute_path,
            "rootUri": root_uri,
            "workspaceFolders": [
                {
                    "uri": root_uri,
                    "name": os.path.basename(repository_absolute_path),
                }
            ],
        }
        return initialize_params  # type: ignore

    def _start_server(self) -> None:
        """Start R Language Server process."""

        def window_log_message(msg: dict) -> None:
            log.info(f"R LSP: window/logMessage: {msg}")

        def do_nothing(params: Any) -> None:
            return

        def register_capability_handler(params: Any) -> None:
            return

        # Register LSP message handlers
        self.server.on_request("client/registerCapability", register_capability_handler)
        self.server.on_notification("window/logMessage", window_log_message)
        self.server.on_notification("$/progress", do_nothing)
        self.server.on_notification("textDocument/publishDiagnostics", do_nothing)

        log.info("Starting R Language Server process")
        self.server.start()

        initialize_params = self._get_initialize_params(self.repository_root_path)
        log.info(
            "Sending initialize request to R Language Server",
        )

        init_response = self.server.send.initialize(initialize_params)

        # Verify server capabilities
        capabilities = init_response.get("capabilities", {})
        assert "textDocumentSync" in capabilities
        if "completionProvider" in capabilities:
            log.info("R LSP completion provider available")
        if "definitionProvider" in capabilities:
            log.info("R LSP definition provider available")

        self.server.notify.initialized({})
        self.completions_available.set()

        # R Language Server is ready after initialization
        self.server_ready.set()

```

--------------------------------------------------------------------------------
/src/solidlsp/language_servers/julia_server.py:
--------------------------------------------------------------------------------

```python
import logging
import os
import pathlib
import platform
import shutil
import subprocess
from typing import Any

from overrides import override

from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings

log = logging.getLogger(__name__)


class JuliaLanguageServer(SolidLanguageServer):
    """
    Language server implementation for Julia using LanguageServer.jl.
    """

    def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
        julia_executable = self._setup_runtime_dependency()  # PASS LOGGER
        julia_code = "using LanguageServer; runserver()"

        julia_ls_cmd: str | list[str]
        if platform.system() == "Windows":
            # On Windows, pass as list (Serena handles shell=True differently)
            julia_ls_cmd = [julia_executable, "--startup-file=no", "--history-file=no", "-e", julia_code, repository_root_path]
        else:
            # On Linux/macOS, build shell-escaped string
            import shlex

            julia_ls_cmd = (
                f"{shlex.quote(julia_executable)} "
                f"--startup-file=no "
                f"--history-file=no "
                f"-e {shlex.quote(julia_code)} "
                f"{shlex.quote(repository_root_path)}"
            )

        log.info(f"[JULIA DEBUG] Command: {julia_ls_cmd}")

        super().__init__(
            config, repository_root_path, ProcessLaunchInfo(cmd=julia_ls_cmd, cwd=repository_root_path), "julia", solidlsp_settings
        )

    @staticmethod
    def _setup_runtime_dependency() -> str:
        """
        Check if the Julia runtime is available and return its full path.
        Raises RuntimeError with a helpful message if the dependency is missing.
        """
        # First check if julia is in PATH
        julia_path = shutil.which("julia")

        # If not found in PATH, check common installation locations
        if julia_path is None:
            common_locations = [
                os.path.expanduser("~/.juliaup/bin/julia"),
                os.path.expanduser("~/.julia/bin/julia"),
                "/usr/local/bin/julia",
                "/usr/bin/julia",
            ]

            for location in common_locations:
                if os.path.isfile(location) and os.access(location, os.X_OK):
                    julia_path = location
                    break

        if julia_path is None:
            raise RuntimeError(
                "Julia is not installed or not in your PATH. "
                "Please install Julia from https://julialang.org/downloads/ and ensure it is accessible. "
                f"Checked locations: {common_locations}"
            )

        # Check if LanguageServer.jl is installed
        check_cmd = [julia_path, "-e", "using LanguageServer"]
        try:
            result = subprocess.run(check_cmd, check=False, capture_output=True, text=True, timeout=10)
            if result.returncode != 0:
                # LanguageServer.jl not found, install it
                JuliaLanguageServer._install_language_server(julia_path)
        except subprocess.TimeoutExpired:
            # Assume it needs installation
            JuliaLanguageServer._install_language_server(julia_path)

        return julia_path

    @staticmethod
    def _install_language_server(julia_path: str) -> None:
        """Install LanguageServer.jl package."""
        log.info("LanguageServer.jl not found. Installing... (this may take a minute)")

        install_cmd = [julia_path, "-e", 'using Pkg; Pkg.add("LanguageServer")']

        try:
            result = subprocess.run(install_cmd, check=False, capture_output=True, text=True, timeout=300)  # 5 minutes for installation

            if result.returncode == 0:
                log.info("LanguageServer.jl installed successfully!")
            else:
                raise RuntimeError(f"Failed to install LanguageServer.jl: {result.stderr}")
        except subprocess.TimeoutExpired:
            raise RuntimeError(
                "LanguageServer.jl installation timed out. Please install manually: julia -e 'using Pkg; Pkg.add(\"LanguageServer\")'"
            )

    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        """Define language-specific directories to ignore for Julia projects."""
        return super().is_ignored_dirname(dirname) or dirname in [".julia", "build", "dist"]

    def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
        """
        Returns the initialize params for the Julia Language Server.
        """
        root_uri = pathlib.Path(repository_absolute_path).as_uri()
        initialize_params: InitializeParams = {  # type: ignore
            "processId": os.getpid(),
            "rootPath": repository_absolute_path,
            "rootUri": root_uri,
            "capabilities": {
                "workspace": {"workspaceFolders": True},
                "textDocument": {
                    "definition": {"dynamicRegistration": True},
                    "references": {"dynamicRegistration": True},
                    "documentSymbol": {"dynamicRegistration": True},
                },
            },
            "workspaceFolders": [
                {
                    "uri": root_uri,
                    "name": os.path.basename(repository_absolute_path),
                }
            ],
        }
        return initialize_params  # type: ignore

    def _start_server(self) -> None:
        """Start the LanguageServer.jl server process."""

        def do_nothing(params: Any) -> None:
            return

        def window_log_message(msg: dict) -> None:
            log.info(f"LSP: window/logMessage: {msg}")

        self.server.on_notification("window/logMessage", window_log_message)
        self.server.on_notification("$/progress", do_nothing)
        self.server.on_notification("textDocument/publishDiagnostics", do_nothing)

        log.info("Starting LanguageServer.jl server process")
        self.server.start()

        initialize_params = self._get_initialize_params(self.repository_root_path)
        log.info("Sending initialize request to Julia Language Server")

        init_response = self.server.send.initialize(initialize_params)
        assert "definitionProvider" in init_response["capabilities"]
        assert "referencesProvider" in init_response["capabilities"]
        assert "documentSymbolProvider" in init_response["capabilities"]

        self.server.notify.initialized({})
        self.completions_available.set()
        log.info("Julia Language Server is initialized and ready.")

```

--------------------------------------------------------------------------------
/src/solidlsp/language_servers/common.py:
--------------------------------------------------------------------------------

```python
from __future__ import annotations

import logging
import os
import platform
import subprocess
from collections.abc import Iterable, Mapping, Sequence
from dataclasses import dataclass, replace
from typing import Any, cast

from solidlsp.ls_utils import FileUtils, PlatformUtils
from solidlsp.util.subprocess_util import subprocess_kwargs

log = logging.getLogger(__name__)


@dataclass(kw_only=True)
class RuntimeDependency:
    """Represents a runtime dependency for a language server."""

    id: str
    platform_id: str | None = None
    url: str | None = None
    archive_type: str | None = None
    binary_name: str | None = None
    command: str | list[str] | None = None
    package_name: str | None = None
    package_version: str | None = None
    extract_path: str | None = None
    description: str | None = None


class RuntimeDependencyCollection:
    """Utility to handle installation of runtime dependencies."""

    def __init__(self, dependencies: Sequence[RuntimeDependency], overrides: Iterable[Mapping[str, Any]] = ()) -> None:
        """Initialize the collection with a list of dependencies and optional overrides.

        :param dependencies: List of base RuntimeDependency instances. The combination of 'id' and 'platform_id' must be unique.
        :param overrides: List of dictionaries which represent overrides or additions to the base dependencies.
            Each entry must contain at least the 'id' key, and optionally 'platform_id' to uniquely identify the dependency to override.
        """
        self._id_and_platform_id_to_dep: dict[tuple[str, str | None], RuntimeDependency] = {}
        for dep in dependencies:
            dep_key = (dep.id, dep.platform_id)
            if dep_key in self._id_and_platform_id_to_dep:
                raise ValueError(f"Duplicate runtime dependency with id '{dep.id}' and platform_id '{dep.platform_id}':\n{dep}")
            self._id_and_platform_id_to_dep[dep_key] = dep

        for dep_values_override in overrides:
            override_key = cast(tuple[str, str | None], (dep_values_override["id"], dep_values_override.get("platform_id")))
            base_dep = self._id_and_platform_id_to_dep.get(override_key)
            if base_dep is None:
                new_runtime_dep = RuntimeDependency(**dep_values_override)
                self._id_and_platform_id_to_dep[override_key] = new_runtime_dep
            else:
                self._id_and_platform_id_to_dep[override_key] = replace(base_dep, **dep_values_override)

    def get_dependencies_for_platform(self, platform_id: str) -> list[RuntimeDependency]:
        return [d for d in self._id_and_platform_id_to_dep.values() if d.platform_id in (platform_id, "any", "platform-agnostic", None)]

    def get_dependencies_for_current_platform(self) -> list[RuntimeDependency]:
        return self.get_dependencies_for_platform(PlatformUtils.get_platform_id().value)

    def get_single_dep_for_current_platform(self, dependency_id: str | None = None) -> RuntimeDependency:
        deps = self.get_dependencies_for_current_platform()
        if dependency_id is not None:
            deps = [d for d in deps if d.id == dependency_id]
        if len(deps) != 1:
            raise RuntimeError(
                f"Expected exactly one runtime dependency for platform-{PlatformUtils.get_platform_id().value} and {dependency_id=}, found {len(deps)}"
            )
        return deps[0]

    def binary_path(self, target_dir: str) -> str:
        dep = self.get_single_dep_for_current_platform()
        if not dep.binary_name:
            return target_dir
        return os.path.join(target_dir, dep.binary_name)

    def install(self, target_dir: str) -> dict[str, str]:
        """Install all dependencies for the current platform into *target_dir*.

        Returns a mapping from dependency id to the resolved binary path.
        """
        os.makedirs(target_dir, exist_ok=True)
        results: dict[str, str] = {}
        for dep in self.get_dependencies_for_current_platform():
            if dep.url:
                self._install_from_url(dep, target_dir)
            if dep.command:
                self._run_command(dep.command, target_dir)
            if dep.binary_name:
                results[dep.id] = os.path.join(target_dir, dep.binary_name)
            else:
                results[dep.id] = target_dir
        return results

    @staticmethod
    def _run_command(command: str | list[str], cwd: str) -> None:
        kwargs = subprocess_kwargs()
        if not PlatformUtils.get_platform_id().is_windows():
            import pwd

            kwargs["user"] = pwd.getpwuid(os.getuid()).pw_name  # type: ignore

        is_windows = platform.system() == "Windows"
        if not isinstance(command, str) and not is_windows:
            # Since we are using the shell, we need to convert the command list to a single string
            # on Linux/macOS
            command = " ".join(command)

        log.info("Running command %s in '%s'", f"'{command}'" if isinstance(command, str) else command, cwd)

        completed_process = subprocess.run(
            command,
            shell=True,
            check=True,
            cwd=cwd,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            **kwargs,
        )  # type: ignore
        if completed_process.returncode != 0:
            log.warning("Command '%s' failed with return code %d", command, completed_process.returncode)
            log.warning("Command output:\n%s", completed_process.stdout)
        else:
            log.info(
                "Command completed successfully",
            )

    @staticmethod
    def _install_from_url(dep: RuntimeDependency, target_dir: str) -> None:
        if not dep.url:
            raise ValueError(f"Dependency {dep.id} has no URL")

        if dep.archive_type in ("gz", "binary") and dep.binary_name:
            dest = os.path.join(target_dir, dep.binary_name)
            FileUtils.download_and_extract_archive(dep.url, dest, dep.archive_type)
        else:
            FileUtils.download_and_extract_archive(dep.url, target_dir, dep.archive_type or "zip")


def quote_windows_path(path: str) -> str:
    """
    Quote a path for Windows command execution if needed.

    On Windows, paths need to be quoted for proper command execution.
    The function checks if the path is already quoted to avoid double-quoting.
    On other platforms, the path is returned unchanged.

    Args:
        path: The file path to potentially quote

    Returns:
        The quoted path on Windows (if not already quoted), unchanged path on other platforms

    """
    if platform.system() == "Windows":
        # Check if already quoted to avoid double-quoting
        if path.startswith('"') and path.endswith('"'):
            return path
        return f'"{path}"'
    return path

```

--------------------------------------------------------------------------------
/test/conftest.py:
--------------------------------------------------------------------------------

```python
import logging
import os
from collections.abc import Iterator
from pathlib import Path

import pytest
from blib2to3.pgen2.parse import contextmanager
from sensai.util.logging import configure

from serena.config.serena_config import SerenaPaths
from serena.constants import SERENA_MANAGED_DIR_NAME
from serena.project import Project
from serena.util.file_system import GitignoreParser
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.settings import SolidLSPSettings

from .solidlsp.clojure import is_clojure_cli_available

configure(level=logging.INFO)

log = logging.getLogger(__name__)


@pytest.fixture(scope="session")
def resources_dir() -> Path:
    """Path to the test resources directory."""
    current_dir = Path(__file__).parent
    return current_dir / "resources"


class LanguageParamRequest:
    param: Language


def get_repo_path(language: Language) -> Path:
    return Path(__file__).parent / "resources" / "repos" / language / "test_repo"


def _create_ls(
    language: Language, repo_path: str | None = None, ignored_paths: list[str] | None = None, trace_lsp_communication: bool = False
) -> SolidLanguageServer:
    ignored_paths = ignored_paths or []
    if repo_path is None:
        repo_path = str(get_repo_path(language))
    gitignore_parser = GitignoreParser(str(repo_path))
    for spec in gitignore_parser.get_ignore_specs():
        ignored_paths.extend(spec.patterns)
    config = LanguageServerConfig(code_language=language, ignored_paths=ignored_paths, trace_lsp_communication=trace_lsp_communication)
    return SolidLanguageServer.create(
        config,
        repo_path,
        solidlsp_settings=SolidLSPSettings(
            solidlsp_dir=SerenaPaths().serena_user_home_dir, project_data_relative_path=SERENA_MANAGED_DIR_NAME
        ),
    )


@contextmanager
def start_ls_context(
    language: Language, repo_path: str | None = None, ignored_paths: list[str] | None = None, trace_lsp_communication: bool = False
) -> Iterator[SolidLanguageServer]:
    ls = _create_ls(language, repo_path, ignored_paths, trace_lsp_communication)
    log.info(f"Starting language server for {language} {repo_path}")
    ls.start()
    try:
        log.info(f"Language server started for {language} {repo_path}")
        yield ls
    finally:
        log.info(f"Stopping language server for {language} {repo_path}")
        try:
            ls.stop(shutdown_timeout=5)
        except Exception as e:
            log.warning(f"Warning: Error stopping language server: {e}")
            # try to force cleanup
            if hasattr(ls, "server") and hasattr(ls.server, "process"):
                try:
                    ls.server.process.terminate()
                except:
                    pass


@contextmanager
def start_default_ls_context(language: Language) -> Iterator[SolidLanguageServer]:
    with start_ls_context(language) as ls:
        yield ls


def _create_default_project(language: Language) -> Project:
    repo_path = str(get_repo_path(language))
    return Project.load(repo_path)


@pytest.fixture(scope="session")
def repo_path(request: LanguageParamRequest) -> Path:
    """Get the repository path for a specific language.

    This fixture requires a language parameter via pytest.mark.parametrize:

    Example:
    ```
    @pytest.mark.parametrize("repo_path", [Language.PYTHON], indirect=True)
    def test_python_repo(repo_path):
        assert (repo_path / "src").exists()
    ```

    """
    if not hasattr(request, "param"):
        raise ValueError("Language parameter must be provided via pytest.mark.parametrize")

    language = request.param
    return get_repo_path(language)


# Note: using module scope here to avoid restarting LS for each test function but still terminate between test modules
@pytest.fixture(scope="module")
def language_server(request: LanguageParamRequest):
    """Create a language server instance configured for the specified language.

    This fixture requires a language parameter via pytest.mark.parametrize:

    Example:
    ```
    @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
    def test_python_server(language_server: SyncLanguageServer) -> None:
        # Use the Python language server
        pass
    ```

    You can also test multiple languages in a single test:
    ```
    @pytest.mark.parametrize("language_server", [Language.PYTHON, Language.TYPESCRIPT], indirect=True)
    def test_multiple_languages(language_server: SyncLanguageServer) -> None:
        # This test will run once for each language
        pass
    ```

    """
    if not hasattr(request, "param"):
        raise ValueError("Language parameter must be provided via pytest.mark.parametrize")

    language = request.param
    with start_default_ls_context(language) as ls:
        yield ls


@pytest.fixture(scope="module")
def project(request: LanguageParamRequest):
    """Create a Project for the specified language.

    This fixture requires a language parameter via pytest.mark.parametrize:

    Example:
    ```
    @pytest.mark.parametrize("project", [Language.PYTHON], indirect=True)
    def test_python_project(project: Project) -> None:
        # Use the Python project to test something
        pass
    ```

    You can also test multiple languages in a single test:
    ```
    @pytest.mark.parametrize("project", [Language.PYTHON, Language.TYPESCRIPT], indirect=True)
    def test_multiple_languages(project: SyncLanguageServer) -> None:
        # This test will run once for each language
        pass
    ```

    """
    if not hasattr(request, "param"):
        raise ValueError("Language parameter must be provided via pytest.mark.parametrize")

    language = request.param
    project = _create_default_project(language)
    yield project
    project.shutdown(timeout=5)


is_ci = os.getenv("CI") == "true" or os.getenv("GITHUB_ACTIONS") == "true"
"""
Flag indicating whether the tests are running in the GitHub CI environment.
"""


def _determine_disabled_languages() -> list[Language]:
    """
    Determine which language tests should be disabled (based on the environment)

    :return: the list of disabled languages
    """
    result: list[Language] = []

    java_tests_enabled = True
    if not java_tests_enabled:
        result.append(Language.JAVA)

    clojure_tests_enabled = is_clojure_cli_available()
    if not clojure_tests_enabled:
        result.append(Language.CLOJURE)

    al_tests_enabled = True
    if not al_tests_enabled:
        result.append(Language.AL)

    return result


_disabled_languages = _determine_disabled_languages()


def language_tests_enabled(language: Language) -> bool:
    """
    Check if tests for the given language are enabled in the current environment.

    :param language: the language to check
    :return: True if tests for the language are enabled, False otherwise
    """
    return language not in _disabled_languages

```

--------------------------------------------------------------------------------
/test/resources/repos/vue/test_repo/src/components/CalculatorInput.vue:
--------------------------------------------------------------------------------

```vue
<script setup lang="ts">
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
import { useCalculatorStore } from '@/stores/calculator'
import { useFormatter } from '@/composables/useFormatter'
import CalculatorButton from './CalculatorButton.vue'
import type { Operation } from '@/types'

// Get the calculator store
const store = useCalculatorStore()

// Use composable for formatting
const formatter = useFormatter(2)

// Local refs for component state
const isOperationPending = ref(false)
const lastOperation = ref<Operation>(null)
const keyboardEnabled = ref(true)
const operationHistory = ref<string[]>([])

// Template refs - demonstrates template ref pattern
const displayRef = ref<HTMLDivElement | null>(null)
const equalsButtonRef = ref<InstanceType<typeof CalculatorButton> | null>(null)

// Computed property for button styling
const getOperationClass = computed(() => (op: Operation) => {
  return lastOperation.value === op ? 'active' : ''
})

// Computed formatted display value using composable
const formattedDisplay = computed(() => {
  const value = parseFloat(store.display)
  return isNaN(value) ? store.display : formatter.formatNumber(value)
})

// Watch for operation changes - demonstrates watch
watch(lastOperation, (newOp, oldOp) => {
  if (newOp !== oldOp && newOp) {
    operationHistory.value.push(newOp)
    // Keep only last 10 operations
    if (operationHistory.value.length > 10) {
      operationHistory.value.shift()
    }
  }
})

// Watch store display changes - demonstrates watch with callback
watch(
  () => store.display,
  (newDisplay) => {
    if (displayRef.value) {
      // Trigger animation on display change
      displayRef.value.classList.add('display-updated')
      setTimeout(() => {
        displayRef.value?.classList.remove('display-updated')
      }, 300)
    }
  }
)

// Lifecycle hook - demonstrates onMounted
onMounted(() => {
  console.log('CalculatorInput mounted')
  // Add keyboard event listener
  window.addEventListener('keydown', handleKeyboard)

  // Focus on the display element
  if (displayRef.value) {
    displayRef.value.focus()
  }
})

// Lifecycle hook - demonstrates onBeforeUnmount
onBeforeUnmount(() => {
  console.log('CalculatorInput unmounting')
  // Clean up keyboard event listener
  window.removeEventListener('keydown', handleKeyboard)
})

// Handle number button clicks
const handleDigit = (digit: number) => {
  store.appendDigit(digit)
  isOperationPending.value = false
}

// Handle operation button clicks
const handleOperation = (operation: Operation) => {
  isOperationPending.value = true
  lastOperation.value = operation

  switch (operation) {
    case 'add':
      store.add()
      break
    case 'subtract':
      store.subtract()
      break
    case 'multiply':
      store.multiply()
      break
    case 'divide':
      store.divide()
      break
  }
}

// Handle equals button
const handleEquals = () => {
  store.equals()
  isOperationPending.value = false
  lastOperation.value = null

  // Access exposed method from child component
  if (equalsButtonRef.value) {
    console.log('Equals button press count:', equalsButtonRef.value.pressCount)
  }
}

// Handle clear button
const handleClear = () => {
  store.clear()
  isOperationPending.value = false
  lastOperation.value = null
  operationHistory.value = []
}

// Keyboard handler - demonstrates event handling
const handleKeyboard = (event: KeyboardEvent) => {
  if (!keyboardEnabled.value) return

  const key = event.key

  if (key >= '0' && key <= '9') {
    handleDigit(parseInt(key))
  } else if (key === '+') {
    handleOperation('add')
  } else if (key === '-') {
    handleOperation('subtract')
  } else if (key === '*') {
    handleOperation('multiply')
  } else if (key === '/') {
    event.preventDefault()
    handleOperation('divide')
  } else if (key === 'Enter' || key === '=') {
    handleEquals()
  } else if (key === 'Escape' || key === 'c' || key === 'C') {
    handleClear()
  }
}

// Toggle keyboard input
const toggleKeyboard = () => {
  keyboardEnabled.value = !keyboardEnabled.value
}

// Array of digits for rendering
const digits = [7, 8, 9, 4, 5, 6, 1, 2, 3, 0]
</script>

<template>
  <div class="calculator-input">
    <div ref="displayRef" class="display" tabindex="0">
      {{ formattedDisplay }}
    </div>

    <div class="keyboard-toggle">
      <label>
        <input type="checkbox" v-model="keyboardEnabled" @change="toggleKeyboard" />
        Enable Keyboard Input
      </label>
    </div>

    <div class="buttons">
      <CalculatorButton
        v-for="digit in digits"
        :key="digit"
        :label="digit"
        variant="digit"
        @click="handleDigit"
      />

      <CalculatorButton
        label="+"
        variant="operation"
        :active="lastOperation === 'add'"
        @click="() => handleOperation('add')"
      />

      <CalculatorButton
        label="-"
        variant="operation"
        :active="lastOperation === 'subtract'"
        @click="() => handleOperation('subtract')"
      />

      <CalculatorButton
        label="×"
        variant="operation"
        :active="lastOperation === 'multiply'"
        @click="() => handleOperation('multiply')"
      />

      <CalculatorButton
        label="÷"
        variant="operation"
        :active="lastOperation === 'divide'"
        @click="() => handleOperation('divide')"
      />

      <CalculatorButton
        ref="equalsButtonRef"
        label="="
        variant="equals"
        size="large"
        @click="handleEquals"
      />

      <CalculatorButton
        label="C"
        variant="clear"
        @click="handleClear"
      />
    </div>

    <div v-if="isOperationPending" class="pending-indicator">
      Operation pending: {{ lastOperation }}
    </div>

    <div v-if="operationHistory.length > 0" class="operation-history">
      Recent operations: {{ operationHistory.join(', ') }}
    </div>
  </div>
</template>

<style scoped>
.calculator-input {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 1rem;
  background: #f5f5f5;
  border-radius: 8px;
}

.display {
  font-size: 2rem;
  text-align: right;
  padding: 1rem;
  background: white;
  border-radius: 4px;
  min-height: 3rem;
  transition: background-color 0.3s;
  outline: none;
}

.display:focus {
  box-shadow: 0 0 0 2px #2196f3;
}

.display.display-updated {
  background-color: #e3f2fd;
}

.keyboard-toggle {
  display: flex;
  justify-content: center;
  padding: 0.5rem;
}

.keyboard-toggle label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
  font-size: 0.9rem;
}

.buttons {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.5rem;
}

.pending-indicator {
  font-size: 0.9rem;
  color: #666;
  text-align: center;
  font-style: italic;
}

.operation-history {
  font-size: 0.8rem;
  color: #999;
  text-align: center;
  padding: 0.5rem;
  background: white;
  border-radius: 4px;
  max-height: 3rem;
  overflow: auto;
}
</style>

```

--------------------------------------------------------------------------------
/.serena/memories/adding_new_language_support_guide.md:
--------------------------------------------------------------------------------

```markdown
# Adding New Language Support to Serena

This guide explains how to add support for a new programming language to Serena.

## Overview

Adding a new language involves:

1. **Language Server Implementation** - Creating a language-specific server class
2. **Language Registration** - Adding the language to enums and configurations  
3. **Test Repository** - Creating a minimal test project
4. **Test Suite** - Writing comprehensive tests
5. **Runtime Dependencies** - Configuring automatic language server downloads

## Step 1: Language Server Implementation

### 1.1 Create Language Server Class

Create a new file in `src/solidlsp/language_servers/` (e.g., `new_language_server.py`).
Have a look at `intelephense.py` for a reference implementation of a language server which downloads all its dependencies, at `gopls.py` for an LS that needs some preinstalled
dependencies, and on `pyright_server.py` that does not need any additional dependencies
because the language server can be installed directly as python package.

```python
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo


class NewLanguageServer(SolidLanguageServer):
    """
    Language server implementation for NewLanguage.
    """

    def __init__(self, config: LanguageServerConfig, repository_root_path: str):
        # Determine language server command
        cmd = self._get_language_server_command()

        super().__init__(config,
            ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path),
            "new_language",,

    def _get_language_server_command(self) -> list[str]:
        """Get the command to start the language server."""
        # Example: return ["new-language-server", "--stdio"]
        pass

    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        """Define language-specific directories to ignore."""
        return super().is_ignored_dirname(dirname) or dirname in ["build",
            "dist", "target"]
```

### 1.2 Language Server Discovery and Installation

For languages requiring automatic installation, implement download logic similar to C#:

```python
@classmethod
def _ensure_server_installed(cls) -> str:
    """Ensure language server is installed and return path."""
    # Check system installation first
    system_server = shutil.which("new-language-server")
    if system_server:
        return system_server
    
    # Download and install if needed
    server_path = cls._download_and_install_server()
    return server_path

def _download_and_install_server(cls) -> str:
    """Download and install the language server."""
    # Implementation specific to your language server
    pass
```

### 1.3 LSP Initialization

Override initialization methods if needed:

```python
def _get_initialize_params(self) -> InitializeParams:
    """Return language-specific initialization parameters."""
    return {
        "processId": os.getpid(),
        "rootUri": PathUtils.path_to_uri(self.repository_root_path),
        "capabilities": {
            # Language-specific capabilities
        }
    }

def _start_server(self):
    """Start the language server with custom handlers."""
    # Set up notification handlers
    self.server.on_notification("window/logMessage", self._handle_log_message)
    
    # Start server and initialize
    self.server.start()
    init_response = self.server.send.initialize(self._get_initialize_params())
    self.server.notify.initialized({})
```

## Step 2: Language Registration

### 2.1 Add to Language Enum

In `src/solidlsp/ls_config.py`, add your language to the `Language` enum:

```python
class Language(str, Enum):
    # Existing languages...
    NEW_LANGUAGE = "new_language"
    
    def get_source_fn_matcher(self) -> FilenameMatcher:
        match self:
            # Existing cases...
            case self.NEW_LANGUAGE:
                return FilenameMatcher("*.newlang", "*.nl")  # File extensions
```

### 2.2 Update Language Server Factory

In `src/solidlsp/ls.py`, add your language to the `create` method:

```python
@classmethod
def create(cls, config: LanguageServerConfig, repository_root_path: str) -> "SolidLanguageServer":
    match config.code_language:
        # Existing cases...
        case Language.NEW_LANGUAGE:
            from solidlsp.language_servers.new_language_server import NewLanguageServer
            return NewLanguageServer(config, repository_root_path)
```

## Step 3: Test Repository

### 3.1 Create Test Project

Create a minimal project in `test/resources/repos/new_language/test_repo/`:

```
test/resources/repos/new_language/test_repo/
├── main.newlang              # Main source file
├── lib/
│   └── helper.newlang       # Additional source for testing
├── project.toml             # Project configuration (if applicable)
└── .gitignore              # Ignore build artifacts
```

### 3.2 Example Source Files

Create meaningful source files that demonstrate:

- **Classes/Types** - For symbol testing
- **Functions/Methods** - For reference finding
- **Imports/Dependencies** - For cross-file operations
- **Nested Structures** - For hierarchical symbol testing

Example `main.newlang`:
```
import lib.helper

class Calculator {
    func add(a: Int, b: Int) -> Int {
        return a + b
    }
    
    func subtract(a: Int, b: Int) -> Int {
        return helper.subtract(a, b)  // Reference to imported function
    }
}

class Program {
    func main() {
        let calc = Calculator()
        let result = calc.add(5, 3)  // Reference to add method
        print(result)
    }
}
```

## Step 4: Test Suite

Testing the language server implementation is of crucial importance, and the tests will
form the main part of the review process. Make sure that the tests are up to the standard
of Serena to make the review go smoother.

General rules for tests:

1. Tests for symbols and references should always check that the expected symbol names and references were actually found.
   Just testing that a list came back or that the result is not None is insufficient.
2. Tests should never be skipped, the only exception is skipping based on some package being available or on an unsupported OS.
3. Tests should run in CI, check if there is a suitable GitHub action for installing the dependencies.

### 4.1 Basic Tests

Create `test/solidlsp/new_language/test_new_language_basic.py`.
Have a look at the structure of existing tests, for example, in `test/solidlsp/php/test_php_basic.py`
You should at least test:

1. Finding symbols
2. Finding within-file references
3. Finding cross-file references

Have a look at `test/solidlsp/php/test_php_basic.py` as an example for what should be tested.
Don't forget to add a new language marker to `pytest.ini`.

### 4.2 Integration Tests

Consider adding new cases to the parametrized tests in `test_serena_agent.py` for the new language.


### 5 Documentation

Update:

- **README.md** - Add language to supported languages list
- **CHANGELOG.md** - Document the new language support
- **Language-specific docs** - Installation requirements, known issues

```

--------------------------------------------------------------------------------
/src/solidlsp/language_servers/scala_language_server.py:
--------------------------------------------------------------------------------

```python
"""
Provides Scala specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Scala.
"""

import logging
import os
import pathlib
import shutil
import subprocess

from overrides import override

from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_utils import PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings

if not PlatformUtils.get_platform_id().value.startswith("win"):
    pass


log = logging.getLogger(__name__)


class ScalaLanguageServer(SolidLanguageServer):
    """
    Provides Scala specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Scala.
    """

    def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
        """
        Creates a ScalaLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
        """
        scala_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
        super().__init__(
            config,
            repository_root_path,
            ProcessLaunchInfo(cmd=scala_lsp_executable_path, cwd=repository_root_path),
            config.code_language.value,
            solidlsp_settings,
        )

    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        return super().is_ignored_dirname(dirname) or dirname in [
            ".bloop",
            ".metals",
            "target",
        ]

    @classmethod
    def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> list[str]:
        """
        Setup runtime dependencies for Scala Language Server and return the command to start the server.
        """
        assert shutil.which("java") is not None, "JDK is not installed or not in PATH."

        metals_version = "1.6.4"

        metals_home = os.path.join(cls.ls_resources_dir(solidlsp_settings), "metals-lsp")
        os.makedirs(metals_home, exist_ok=True)
        metals_executable = os.path.join(metals_home, metals_version, "metals")
        coursier_command_path = shutil.which("coursier")
        cs_command_path = shutil.which("cs")
        assert cs_command_path is not None or coursier_command_path is not None, "coursier is not installed or not in PATH."

        if not os.path.exists(metals_executable):
            if not cs_command_path:
                assert coursier_command_path is not None
                log.info("'cs' command not found. Trying to install it using 'coursier'.")
                try:
                    log.info("Running 'coursier setup --yes' to install 'cs'...")
                    subprocess.run([coursier_command_path, "setup", "--yes"], check=True, capture_output=True, text=True)
                except subprocess.CalledProcessError as e:
                    raise RuntimeError(f"Failed to set up 'cs' command with 'coursier setup'. Stderr: {e.stderr}")

                cs_command_path = shutil.which("cs")
                if not cs_command_path:
                    raise RuntimeError(
                        "'cs' command not found after running 'coursier setup'. Please check your PATH or install it manually."
                    )
                log.info("'cs' command installed successfully.")

            log.info(f"metals executable not found at {metals_executable}, bootstrapping...")
            subprocess.run(["mkdir", "-p", os.path.join(metals_home, metals_version)], check=True)
            artifact = f"org.scalameta:metals_2.13:{metals_version}"
            cmd = [
                cs_command_path,
                "bootstrap",
                "--java-opt",
                "-XX:+UseG1GC",
                "--java-opt",
                "-XX:+UseStringDeduplication",
                "--java-opt",
                "-Xss4m",
                "--java-opt",
                "-Xms100m",
                "--java-opt",
                "-Dmetals.client=Serena",
                artifact,
                "-o",
                metals_executable,
                "-f",
            ]
            log.info("Bootstrapping metals...")
            subprocess.run(cmd, cwd=metals_home, check=True)
            log.info("Bootstrapping metals finished.")
        return [metals_executable]

    @staticmethod
    def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
        """
        Returns the initialize params for the Scala Language Server.
        """
        root_uri = pathlib.Path(repository_absolute_path).as_uri()
        initialize_params = {
            "locale": "en",
            "processId": os.getpid(),
            "rootPath": repository_absolute_path,
            "rootUri": root_uri,
            "initializationOptions": {
                "compilerOptions": {
                    "completionCommand": None,
                    "isCompletionItemDetailEnabled": True,
                    "isCompletionItemDocumentationEnabled": True,
                    "isCompletionItemResolve": True,
                    "isHoverDocumentationEnabled": True,
                    "isSignatureHelpDocumentationEnabled": True,
                    "overrideDefFormat": "ascli",
                    "snippetAutoIndent": False,
                },
                "debuggingProvider": True,
                "decorationProvider": False,
                "didFocusProvider": False,
                "doctorProvider": False,
                "executeClientCommandProvider": False,
                "globSyntax": "uri",
                "icons": "unicode",
                "inputBoxProvider": False,
                "isVirtualDocumentSupported": False,
                "isExitOnShutdown": True,
                "isHttpEnabled": True,
                "openFilesOnRenameProvider": False,
                "quickPickProvider": False,
                "renameFileThreshold": 200,
                "statusBarProvider": "false",
                "treeViewProvider": False,
                "testExplorerProvider": False,
                "openNewWindowProvider": False,
                "copyWorksheetOutputProvider": False,
                "doctorVisibilityProvider": False,
            },
            "capabilities": {"textDocument": {"documentSymbol": {"hierarchicalDocumentSymbolSupport": True}}},
        }
        return initialize_params  # type: ignore

    def _start_server(self) -> None:
        """
        Starts the Scala Language Server
        """
        log.info("Starting Scala server process")
        self.server.start()

        log.info("Sending initialize request from LSP client to LSP server and awaiting response")

        initialize_params = self._get_initialize_params(self.repository_root_path)
        self.server.send.initialize(initialize_params)
        self.server.notify.initialized({})

    @override
    def _get_wait_time_for_cross_file_referencing(self) -> float:
        return 5

```

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

```python
"""
Models module that demonstrates various Python class patterns.
"""

from abc import ABC, abstractmethod
from typing import Any, Generic, TypeVar

T = TypeVar("T")


class BaseModel(ABC):
    """
    Abstract base class for all models.
    """

    def __init__(self, id: str, name: str | None = None):
        self.id = id
        self.name = name or id

    @abstractmethod
    def to_dict(self) -> dict[str, Any]:
        """Convert model to dictionary representation"""

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "BaseModel":
        """Create a model instance from dictionary data"""
        id = data.get("id", "")
        name = data.get("name")
        return cls(id=id, name=name)


class User(BaseModel):
    """
    User model representing a system user.
    """

    def __init__(self, id: str, name: str | None = None, email: str = "", roles: list[str] | None = None):
        super().__init__(id, name)
        self.email = email
        self.roles = roles or []

    def to_dict(self) -> dict[str, Any]:
        return {"id": self.id, "name": self.name, "email": self.email, "roles": self.roles}

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "User":
        instance = super().from_dict(data)
        instance.email = data.get("email", "")
        instance.roles = data.get("roles", [])
        return instance

    def has_role(self, role: str) -> bool:
        """Check if user has a specific role"""
        return role in self.roles


class Item(BaseModel):
    """
    Item model representing a product or service.
    """

    def __init__(self, id: str, name: str | None = None, price: float = 0.0, category: str = ""):
        super().__init__(id, name)
        self.price = price
        self.category = category

    def to_dict(self) -> dict[str, Any]:
        return {"id": self.id, "name": self.name, "price": self.price, "category": self.category}

    def get_display_price(self) -> str:
        """Format price for display"""
        return f"${self.price:.2f}"


# Generic type example
class Collection(Generic[T]):
    def __init__(self, items: list[T] | None = None):
        self.items = items or []

    def add(self, item: T) -> None:
        self.items.append(item)

    def get_all(self) -> list[T]:
        return self.items


# Factory function
def create_user_object(id: str, name: str, email: str, roles: list[str] | None = None) -> User:
    """Factory function to create a user"""
    return User(id=id, name=name, email=email, roles=roles)


# Multiple inheritance examples


class Loggable:
    """
    Mixin class that provides logging functionality.
    Example of a common mixin pattern used with multiple inheritance.
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.log_entries: list[str] = []

    def log(self, message: str) -> None:
        """Add a log entry"""
        self.log_entries.append(message)

    def get_logs(self) -> list[str]:
        """Get all log entries"""
        return self.log_entries


class Serializable:
    """
    Mixin class that provides JSON serialization capabilities.
    Another example of a mixin for multiple inheritance.
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def to_json(self) -> dict[str, Any]:
        """Convert to JSON-serializable dictionary"""
        return self.to_dict() if hasattr(self, "to_dict") else {}

    @classmethod
    def from_json(cls, data: dict[str, Any]) -> Any:
        """Create instance from JSON data"""
        return cls.from_dict(data) if hasattr(cls, "from_dict") else cls(**data)


class Auditable:
    """
    Mixin for tracking creation and modification timestamps.
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.created_at: str = kwargs.get("created_at", "")
        self.updated_at: str = kwargs.get("updated_at", "")

    def update_timestamp(self, timestamp: str) -> None:
        """Update the last modified timestamp"""
        self.updated_at = timestamp


# Diamond inheritance pattern
class BaseService(ABC):
    """
    Base class for service objects - demonstrates diamond inheritance pattern.
    """

    def __init__(self, name: str = "base"):
        self.service_name = name

    @abstractmethod
    def get_service_info(self) -> dict[str, str]:
        """Get service information"""


class DataService(BaseService):
    """
    Data handling service.
    """

    def __init__(self, **kwargs):
        name = kwargs.pop("name", "data")
        super().__init__(name=name)
        self.data_source = kwargs.get("data_source", "default")

    def get_service_info(self) -> dict[str, str]:
        return {"service_type": "data", "service_name": self.service_name, "data_source": self.data_source}


class NetworkService(BaseService):
    """
    Network connectivity service.
    """

    def __init__(self, **kwargs):
        name = kwargs.pop("name", "network")
        super().__init__(name=name)
        self.endpoint = kwargs.get("endpoint", "localhost")

    def get_service_info(self) -> dict[str, str]:
        return {"service_type": "network", "service_name": self.service_name, "endpoint": self.endpoint}


class DataSyncService(DataService, NetworkService):
    """
    Service that syncs data over network - example of diamond inheritance.
    Inherits from both DataService and NetworkService, which both inherit from BaseService.
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.sync_interval = kwargs.get("sync_interval", 60)

    def get_service_info(self) -> dict[str, str]:
        info = super().get_service_info()
        info.update({"service_type": "data_sync", "sync_interval": str(self.sync_interval)})
        return info


# Multiple inheritance with mixins


class LoggableUser(User, Loggable):
    """
    User class with logging capabilities.
    Example of extending a concrete class with a mixin.
    """

    def __init__(self, id: str, name: str | None = None, email: str = "", roles: list[str] | None = None):
        super().__init__(id=id, name=name, email=email, roles=roles)

    def add_role(self, role: str) -> None:
        """Add a role to the user and log the action"""
        if role not in self.roles:
            self.roles.append(role)
            self.log(f"Added role '{role}' to user {self.id}")


class TrackedItem(Item, Serializable, Auditable):
    """
    Item with serialization and auditing capabilities.
    Example of a class inheriting from a concrete class and multiple mixins.
    """

    def __init__(
        self, id: str, name: str | None = None, price: float = 0.0, category: str = "", created_at: str = "", updated_at: str = ""
    ):
        super().__init__(id=id, name=name, price=price, category=category, created_at=created_at, updated_at=updated_at)
        self.stock_level = 0

    def update_stock(self, quantity: int) -> None:
        """Update stock level and timestamp"""
        self.stock_level = quantity
        self.update_timestamp(f"stock_update_{quantity}")

    def to_dict(self) -> dict[str, Any]:
        result = super().to_dict()
        result.update({"stock_level": self.stock_level, "created_at": self.created_at, "updated_at": self.updated_at})
        return result

```

--------------------------------------------------------------------------------
/test/solidlsp/rego/test_rego_basic.py:
--------------------------------------------------------------------------------

```python
"""Tests for Rego language server (Regal) functionality."""

import os
import sys

import pytest

from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils


@pytest.mark.rego
@pytest.mark.skipif(
    sys.platform == "win32", reason="Regal LSP has Windows path handling bug - see https://github.com/StyraInc/regal/issues/1683"
)
class TestRegoLanguageServer:
    """Test Regal language server functionality for Rego."""

    @pytest.mark.parametrize("language_server", [Language.REGO], indirect=True)
    def test_request_document_symbols_authz(self, language_server: SolidLanguageServer) -> None:
        """Test that document symbols can be retrieved from authz.rego."""
        file_path = os.path.join("policies", "authz.rego")
        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()

        assert symbols is not None
        assert len(symbols) > 0

        # Extract symbol names
        symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
        symbol_names = {sym.get("name") for sym in symbol_list if isinstance(sym, dict)}

        # Verify specific Rego rules/functions are found
        assert "allow" in symbol_names, "allow rule not found"
        assert "allow_read" in symbol_names, "allow_read rule not found"
        assert "is_admin" in symbol_names, "is_admin function not found"
        assert "admin_roles" in symbol_names, "admin_roles constant not found"

    @pytest.mark.parametrize("language_server", [Language.REGO], indirect=True)
    def test_request_document_symbols_helpers(self, language_server: SolidLanguageServer) -> None:
        """Test that document symbols can be retrieved from helpers.rego."""
        file_path = os.path.join("utils", "helpers.rego")
        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()

        assert symbols is not None
        assert len(symbols) > 0

        # Extract symbol names
        symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
        symbol_names = {sym.get("name") for sym in symbol_list if isinstance(sym, dict)}

        # Verify specific helper functions are found
        assert "is_valid_user" in symbol_names, "is_valid_user function not found"
        assert "is_valid_email" in symbol_names, "is_valid_email function not found"
        assert "is_valid_username" in symbol_names, "is_valid_username function not found"

    @pytest.mark.parametrize("language_server", [Language.REGO], indirect=True)
    def test_find_symbol_full_tree(self, language_server: SolidLanguageServer) -> None:
        """Test finding symbols across entire workspace using symbol tree."""
        symbols = language_server.request_full_symbol_tree()

        # Use SymbolUtils to check for expected symbols
        assert SymbolUtils.symbol_tree_contains_name(symbols, "allow"), "allow rule not found in symbol tree"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "is_valid_user"), "is_valid_user function not found in symbol tree"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "is_admin"), "is_admin function not found in symbol tree"

    @pytest.mark.parametrize("language_server", [Language.REGO], indirect=True)
    def test_request_definition_within_file(self, language_server: SolidLanguageServer) -> None:
        """Test go-to-definition for symbols within the same file."""
        # In authz.rego, check_permission references admin_roles
        file_path = os.path.join("policies", "authz.rego")

        # Get document symbols
        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
        symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols

        # Find the is_admin symbol which references admin_roles
        is_admin_symbol = next((s for s in symbol_list if s.get("name") == "is_admin"), None)
        assert is_admin_symbol is not None, "is_admin symbol should always be found in authz.rego"
        assert "range" in is_admin_symbol, "is_admin symbol should have a range"

        # Request definition from within is_admin (line 25, which references admin_roles at line 21)
        # Line 25 is: admin_roles[_] == user.role
        line = is_admin_symbol["range"]["start"]["line"] + 1
        char = 4  # Position at "admin_roles"

        definitions = language_server.request_definition(file_path, line, char)
        assert definitions is not None and len(definitions) > 0, "Should find definition for admin_roles"

        # Verify the definition points to admin_roles in the same file
        assert any("authz.rego" in defn.get("relativePath", "") for defn in definitions), "Definition should be in authz.rego"

    @pytest.mark.parametrize("language_server", [Language.REGO], indirect=True)
    def test_request_definition_across_files(self, language_server: SolidLanguageServer) -> None:
        """Test go-to-definition for symbols across files (cross-file references)."""
        # In authz.rego line 11, the allow rule calls utils.is_valid_user
        # This function is defined in utils/helpers.rego
        file_path = os.path.join("policies", "authz.rego")

        # Get document symbols
        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
        symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols

        # Find the allow symbol
        allow_symbol = next((s for s in symbol_list if s.get("name") == "allow"), None)
        assert allow_symbol is not None, "allow symbol should always be found in authz.rego"
        assert "range" in allow_symbol, "allow symbol should have a range"

        # Request definition from line 11 where utils.is_valid_user is called
        # Line 11: utils.is_valid_user(input.user)
        line = 10  # 0-indexed, so line 11 in file is line 10 in LSP
        char = 7  # Position at "is_valid_user" in "utils.is_valid_user"

        definitions = language_server.request_definition(file_path, line, char)
        assert definitions is not None and len(definitions) > 0, "Should find cross-file definition for is_valid_user"

        # Verify the definition points to helpers.rego (cross-file)
        assert any(
            "helpers.rego" in defn.get("relativePath", "") for defn in definitions
        ), "Definition should be in utils/helpers.rego (cross-file reference)"

    @pytest.mark.parametrize("language_server", [Language.REGO], indirect=True)
    def test_find_symbols_validation(self, language_server: SolidLanguageServer) -> None:
        """Test finding symbols in validation.rego which has imports."""
        file_path = os.path.join("policies", "validation.rego")
        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()

        assert symbols is not None
        assert len(symbols) > 0

        # Extract symbol names
        symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
        symbol_names = {sym.get("name") for sym in symbol_list if isinstance(sym, dict)}

        # Verify expected symbols
        assert "validate_user_input" in symbol_names, "validate_user_input rule not found"
        assert "has_valid_credentials" in symbol_names, "has_valid_credentials function not found"
        assert "validate_request" in symbol_names, "validate_request rule not found"

```
Page 3/17FirstPrevNextLast