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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/docs/01-about/035_tools.md:
--------------------------------------------------------------------------------

```markdown
 1 | # List of Tools
 2 | 
 3 | Find the full list of Serena's tools below (output of `<serena> tools list --all`).
 4 | 
 5 | Note that in most configurations, only a subset of these tools will be enabled simultaneously (see the section on [configuration](../02-usage/050_configuration) for details).
 6 | 
 7 | * `activate_project`: Activates a project based on the project name or path.
 8 | * `check_onboarding_performed`: Checks whether project onboarding was already performed.
 9 | * `create_text_file`: Creates/overwrites a file in the project directory.
10 | * `delete_lines`: Deletes a range of lines within a file.
11 | * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
12 | * `execute_shell_command`: Executes a shell command.
13 | * `find_file`: Finds files in the given relative paths
14 | * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
15 | * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filter
16 |   ed by type).
17 | * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools,
18 |   contexts, and modes.
19 | * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
20 | * `initial_instructions`: Provides instructions on how to use the Serena toolbox.
21 | * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
22 | * `insert_at_line`: Inserts content at a given line in a file.
23 | * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
24 | * `jet_brains_find_referencing_symbols`: Finds symbols that reference the given symbol
25 | * `jet_brains_find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (option
26 |   ally filtered by type).
27 | * `jet_brains_get_symbols_overview`: Retrieves an overview of the top-level symbols within a specified file
28 | * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
29 | * `list_memories`: Lists memories in Serena's project-specific memory store.
30 | * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
31 | * `open_dashboard`: Opens the Serena web dashboard in the default web browser.
32 | * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with th
33 |   e necessary context).
34 | * `read_file`: Reads a file within the project directory.
35 | * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
36 | * `remove_project`: Removes a project from the Serena configuration.
37 | * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities.
38 | * `replace_lines`: Replaces a range of lines within a file with new content.
39 | * `replace_content`: Replaces content in a file (optionally using regular expressions).
40 | * `replace_symbol_body`: Replaces the full definition of a symbol.
41 | * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
42 | * `search_for_pattern`: Performs a search for a pattern in the project.
43 | * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
44 | * `switch_modes`: Activates modes by providing a list of their names
45 | * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
46 | * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
47 | * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
48 | * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
49 | 
```

--------------------------------------------------------------------------------
/src/interprompt/prompt_factory.py:
--------------------------------------------------------------------------------

```python
 1 | import logging
 2 | import os
 3 | from typing import Any
 4 | 
 5 | from .multilang_prompt import DEFAULT_LANG_CODE, LanguageFallbackMode, MultiLangPromptCollection, PromptList
 6 | 
 7 | log = logging.getLogger(__name__)
 8 | 
 9 | 
10 | class PromptFactoryBase:
11 |     """Base class for auto-generated prompt factory classes."""
12 | 
13 |     def __init__(self, prompts_dir: str | list[str], lang_code: str = DEFAULT_LANG_CODE, fallback_mode=LanguageFallbackMode.EXCEPTION):
14 |         """
15 |         :param prompts_dir: the directory containing the prompt templates and prompt lists.
16 |             If a list is provided, will look for prompt templates in the dirs from left to right
17 |             (first one containing the desired template wins).
18 |         :param lang_code: the language code to use for retrieving the prompt templates and prompt lists.
19 |             Leave as `default` for single-language use cases.
20 |         :param fallback_mode: the fallback mode to use when a prompt template or prompt list is not found for the requested language.
21 |             Irrelevant for single-language use cases.
22 |         """
23 |         self.lang_code = lang_code
24 |         self._prompt_collection = MultiLangPromptCollection(prompts_dir, fallback_mode=fallback_mode)
25 | 
26 |     def _render_prompt(self, prompt_name: str, params: dict[str, Any]) -> str:
27 |         del params["self"]
28 |         return self._prompt_collection.render_prompt_template(prompt_name, params, lang_code=self.lang_code)
29 | 
30 |     def _get_prompt_list(self, prompt_name: str) -> PromptList:
31 |         return self._prompt_collection.get_prompt_list(prompt_name, self.lang_code)
32 | 
33 | 
34 | def autogenerate_prompt_factory_module(prompts_dir: str, target_module_path: str) -> None:
35 |     """
36 |     Auto-generates a prompt factory module for the given prompt directory.
37 |     The generated `PromptFactory` class is meant to be the central entry class for retrieving and rendering prompt templates and prompt
38 |     lists in your application.
39 |     It will contain one method per prompt template and prompt list, and is useful for both single- and multi-language use cases.
40 | 
41 |     :param prompts_dir: the directory containing the prompt templates and prompt lists
42 |     :param target_module_path: the path to the target module file (.py). Important: The module will be overwritten!
43 |     """
44 |     generated_code = """
45 | # ruff: noqa
46 | # black: skip
47 | # mypy: ignore-errors
48 | 
49 | # NOTE: This module is auto-generated from interprompt.autogenerate_prompt_factory_module, do not edit manually!
50 | 
51 | from interprompt.multilang_prompt import PromptList
52 | from interprompt.prompt_factory import PromptFactoryBase
53 | from typing import Any
54 | 
55 | 
56 | class PromptFactory(PromptFactoryBase):
57 |     \"""
58 |     A class for retrieving and rendering prompt templates and prompt lists.
59 |     \"""
60 | """
61 |     # ---- add methods based on prompt template names and parameters and prompt list names ----
62 |     prompt_collection = MultiLangPromptCollection(prompts_dir)
63 | 
64 |     for template_name in prompt_collection.get_prompt_template_names():
65 |         template_parameters = prompt_collection.get_prompt_template_parameters(template_name)
66 |         if len(template_parameters) == 0:
67 |             method_params_str = ""
68 |         else:
69 |             method_params_str = ", *, " + ", ".join([f"{param}: Any" for param in template_parameters])
70 |         generated_code += f"""
71 |     def create_{template_name}(self{method_params_str}) -> str:
72 |         return self._render_prompt('{template_name}', locals())
73 | """
74 |     for prompt_list_name in prompt_collection.get_prompt_list_names():
75 |         generated_code += f"""
76 |     def get_list_{prompt_list_name}(self) -> PromptList:
77 |         return self._get_prompt_list('{prompt_list_name}')
78 | """
79 |     os.makedirs(os.path.dirname(target_module_path), exist_ok=True)
80 |     with open(target_module_path, "w", encoding="utf-8") as f:
81 |         f.write(generated_code)
82 |     log.info(f"Prompt factory generated successfully in {target_module_path}")
83 | 
```

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

```yaml
 1 | # The system prompt template. Note that many clients will not allow configuration of the actual system prompt,
 2 | # in which case this prompt will be given as a regular message on the call of a simple tool which the agent
 3 | # is encouraged (via the tool description) to call at the beginning of the conversation.
 4 | prompts:
 5 |   system_prompt: |
 6 |     You are a professional coding agent. 
 7 |     You have access to semantic coding tools upon which you rely heavily for all your work.
 8 |     You operate in a resource-efficient and intelligent manner, always keeping in mind to not read or generate
 9 |     content that is not needed for the task at hand.
10 | 
11 |     Some tasks may require you to understand the architecture of large parts of the codebase, while for others,
12 |     it may be enough to read a small set of symbols or a single file.
13 |     You avoid reading entire files unless it is absolutely necessary, instead relying on intelligent step-by-step 
14 |     acquisition of information. {% if 'ToolMarkerSymbolicRead' in available_markers %}Once you have read a full file, it does not make
15 |     sense to analyse it with the symbolic read tools; you already have the information.{% endif %}
16 | 
17 |     You can achieve intelligent reading of code by using the symbolic tools for getting an overview of symbols and
18 |     the relations between them, and then only reading the bodies of symbols that are necessary to complete the task at hand. 
19 |     You can use the standard tools like list_dir, find_file and search_for_pattern if you need to.
20 |     Where appropriate, you pass the `relative_path` parameter to restrict the search to a specific file or directory.
21 |     {% if 'search_for_pattern' in available_tools %}
22 |     If you are unsure about a symbol's name or location{% if 'find_symbol' in available_tools %} (to the extent that substring_matching for the symbol name is not enough){% endif %}, you can use the `search_for_pattern` tool, which allows fast
23 |     and flexible search for patterns in the codebase.{% if 'ToolMarkerSymbolicRead' in available_markers %} In this way, you can first find candidates for symbols or files,
24 |     and then proceed with the symbolic tools.{% endif %}
25 |     {% endif %}
26 | 
27 |     {% if 'ToolMarkerSymbolicRead' in available_markers %}
28 |     Symbols are identified by their `name_path` and `relative_path` (see the description of the `find_symbol` tool).
29 |     You can get information about the symbols in a file by using the `get_symbols_overview` tool or use the `find_symbol` to search. 
30 |     You only read the bodies of symbols when you need to (e.g. if you want to fully understand or edit it).
31 |     For example, if you are working with Python code and already know that you need to read the body of the constructor of the class Foo, you can directly
32 |     use `find_symbol` with name path pattern `Foo/__init__` and `include_body=True`. If you don't know yet which methods in `Foo` you need to read or edit,
33 |     you can use `find_symbol` with name path pattern `Foo`, `include_body=False` and `depth=1` to get all (top-level) methods of `Foo` before proceeding
34 |     to read the desired methods with `include_body=True`.
35 |     You can understand relationships between symbols by using the `find_referencing_symbols` tool.
36 |     {% endif %}
37 | 
38 |     {% if 'read_memory' in available_tools %}
39 |     You generally have access to memories and it may be useful for you to read them.
40 |     You infer whether memories are relevant based on their names.
41 |     {% endif %}
42 | 
43 |     The context and modes of operation are described below. These determine how to interact with your user
44 |     and which kinds of interactions are expected of you.
45 | 
46 |     Context description:
47 |     {{ context_system_prompt }}
48 | 
49 |     Modes descriptions:
50 |     {% for prompt in mode_system_prompts %}
51 |     {{ prompt }}
52 |     {% endfor %}
53 |     
54 |     You have hereby read the 'Serena Instructions Manual' and do not need to read it again.
55 | 
```

--------------------------------------------------------------------------------
/test/resources/repos/vue/test_repo/src/stores/calculator.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { defineStore } from 'pinia'
  2 | import type { HistoryEntry, Operation, CalculatorState } from '@/types'
  3 | 
  4 | export const useCalculatorStore = defineStore('calculator', {
  5 |   state: (): CalculatorState => ({
  6 |     currentValue: 0,
  7 |     previousValue: null,
  8 |     operation: null,
  9 |     history: [],
 10 |     displayValue: '0'
 11 |   }),
 12 | 
 13 |   getters: {
 14 |     /**
 15 |      * Get the most recent history entries (last 10)
 16 |      */
 17 |     recentHistory: (state): HistoryEntry[] => {
 18 |       return state.history.slice(-10).reverse()
 19 |     },
 20 | 
 21 |     /**
 22 |      * Check if calculator has any history
 23 |      */
 24 |     hasHistory: (state): boolean => {
 25 |       return state.history.length > 0
 26 |     },
 27 | 
 28 |     /**
 29 |      * Get the current display text
 30 |      */
 31 |     display: (state): string => {
 32 |       return state.displayValue
 33 |     }
 34 |   },
 35 | 
 36 |   actions: {
 37 |     /**
 38 |      * Set a number value
 39 |      */
 40 |     setNumber(value: number) {
 41 |       this.currentValue = value
 42 |       this.displayValue = value.toString()
 43 |     },
 44 | 
 45 |     /**
 46 |      * Append a digit to the current value
 47 |      */
 48 |     appendDigit(digit: number) {
 49 |       if (this.displayValue === '0') {
 50 |         this.displayValue = digit.toString()
 51 |       } else {
 52 |         this.displayValue += digit.toString()
 53 |       }
 54 |       this.currentValue = parseFloat(this.displayValue)
 55 |     },
 56 | 
 57 |     /**
 58 |      * Add two numbers
 59 |      */
 60 |     add() {
 61 |       if (this.previousValue !== null && this.operation) {
 62 |         this.executeOperation()
 63 |       }
 64 |       this.previousValue = this.currentValue
 65 |       this.operation = 'add'
 66 |       this.displayValue = '0'
 67 |     },
 68 | 
 69 |     /**
 70 |      * Subtract two numbers
 71 |      */
 72 |     subtract() {
 73 |       if (this.previousValue !== null && this.operation) {
 74 |         this.executeOperation()
 75 |       }
 76 |       this.previousValue = this.currentValue
 77 |       this.operation = 'subtract'
 78 |       this.displayValue = '0'
 79 |     },
 80 | 
 81 |     /**
 82 |      * Multiply two numbers
 83 |      */
 84 |     multiply() {
 85 |       if (this.previousValue !== null && this.operation) {
 86 |         this.executeOperation()
 87 |       }
 88 |       this.previousValue = this.currentValue
 89 |       this.operation = 'multiply'
 90 |       this.displayValue = '0'
 91 |     },
 92 | 
 93 |     /**
 94 |      * Divide two numbers
 95 |      */
 96 |     divide() {
 97 |       if (this.previousValue !== null && this.operation) {
 98 |         this.executeOperation()
 99 |       }
100 |       this.previousValue = this.currentValue
101 |       this.operation = 'divide'
102 |       this.displayValue = '0'
103 |     },
104 | 
105 |     /**
106 |      * Execute the pending operation
107 |      */
108 |     executeOperation() {
109 |       if (this.previousValue === null || this.operation === null) {
110 |         return
111 |       }
112 | 
113 |       let result = 0
114 |       const prev = this.previousValue
115 |       const current = this.currentValue
116 |       let expression = ''
117 | 
118 |       switch (this.operation) {
119 |         case 'add':
120 |           result = prev + current
121 |           expression = `${prev} + ${current}`
122 |           break
123 |         case 'subtract':
124 |           result = prev - current
125 |           expression = `${prev} - ${current}`
126 |           break
127 |         case 'multiply':
128 |           result = prev * current
129 |           expression = `${prev} × ${current}`
130 |           break
131 |         case 'divide':
132 |           if (current === 0) {
133 |             this.displayValue = 'Error'
134 |             this.clear()
135 |             return
136 |           }
137 |           result = prev / current
138 |           expression = `${prev} ÷ ${current}`
139 |           break
140 |       }
141 | 
142 |       // Add to history
143 |       this.history.push({
144 |         expression,
145 |         result,
146 |         timestamp: new Date()
147 |       })
148 | 
149 |       this.currentValue = result
150 |       this.displayValue = result.toString()
151 |       this.previousValue = null
152 |       this.operation = null
153 |     },
154 | 
155 |     /**
156 |      * Calculate the equals operation
157 |      */
158 |     equals() {
159 |       this.executeOperation()
160 |     },
161 | 
162 |     /**
163 |      * Clear the calculator state
164 |      */
165 |     clear() {
166 |       this.currentValue = 0
167 |       this.previousValue = null
168 |       this.operation = null
169 |       this.displayValue = '0'
170 |     },
171 | 
172 |     /**
173 |      * Clear all history
174 |      */
175 |     clearHistory() {
176 |       this.history = []
177 |     }
178 |   }
179 | })
180 | 
```

--------------------------------------------------------------------------------
/.serena/memories/serena_repository_structure.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Serena Repository Structure
  2 | 
  3 | ## Overview
  4 | Serena is a multi-language code assistant that combines two main components:
  5 | 1. **Serena Core** - The main agent framework with tools and MCP server
  6 | 2. **SolidLSP** - A unified Language Server Protocol wrapper for multiple programming languages
  7 | 
  8 | ## Top-Level Structure
  9 | 
 10 | ```
 11 | serena/
 12 | ├── src/                          # Main source code
 13 | │   ├── serena/                   # Serena agent framework
 14 | │   ├── solidlsp/                 # LSP wrapper library  
 15 | │   └── interprompt/              # Multi-language prompt templates
 16 | ├── test/                         # Test suites
 17 | │   ├── serena/                   # Serena agent tests
 18 | │   ├── solidlsp/                 # Language server tests
 19 | │   └── resources/repos/          # Test repositories for each language
 20 | ├── scripts/                      # Build and utility scripts
 21 | ├── resources/                    # Static resources and configurations
 22 | ├── pyproject.toml               # Python project configuration
 23 | ├── README.md                    # Project documentation
 24 | └── CHANGELOG.md                 # Version history
 25 | ```
 26 | 
 27 | ## Source Code Organization
 28 | 
 29 | ### Serena Core (`src/serena/`)
 30 | - **`agent.py`** - Main SerenaAgent class that orchestrates everything
 31 | - **`tools/`** - MCP tools for file operations, symbols, memory, etc.
 32 |   - `file_tools.py` - File system operations (read, write, search)
 33 |   - `symbol_tools.py` - Symbol-based code operations (find, edit)
 34 |   - `memory_tools.py` - Knowledge persistence and retrieval
 35 |   - `config_tools.py` - Project and mode management
 36 |   - `workflow_tools.py` - Onboarding and meta-operations
 37 | - **`config/`** - Configuration management
 38 |   - `serena_config.py` - Main configuration classes
 39 |   - `context_mode.py` - Context and mode definitions
 40 | - **`util/`** - Utility modules
 41 | - **`mcp.py`** - MCP server implementation
 42 | - **`cli.py`** - Command-line interface
 43 | 
 44 | ### SolidLSP (`src/solidlsp/`)
 45 | - **`ls.py`** - Main SolidLanguageServer class
 46 | - **`language_servers/`** - Language-specific implementations
 47 |   - `csharp_language_server.py` - C# (Microsoft.CodeAnalysis.LanguageServer)
 48 |   - `python_server.py` - Python (Pyright)
 49 |   - `typescript_language_server.py` - TypeScript
 50 |   - `rust_analyzer.py` - Rust
 51 |   - `gopls.py` - Go
 52 |   - And many more...
 53 | - **`ls_config.py`** - Language server configuration
 54 | - **`ls_types.py`** - LSP type definitions
 55 | - **`ls_utils.py`** - Utilities for working with LSP data
 56 | 
 57 | ### Interprompt (`src/interprompt/`)
 58 | - Multi-language prompt template system
 59 | - Jinja2-based templating with language fallbacks
 60 | 
 61 | ## Test Structure
 62 | 
 63 | ### Language Server Tests (`test/solidlsp/`)
 64 | Each language has its own test directory:
 65 | ```
 66 | test/solidlsp/
 67 | ├── csharp/
 68 | │   └── test_csharp_basic.py
 69 | ├── python/
 70 | │   └── test_python_basic.py
 71 | ├── typescript/
 72 | │   └── test_typescript_basic.py
 73 | └── ...
 74 | ```
 75 | 
 76 | ### Test Resources (`test/resources/repos/`)
 77 | Contains minimal test projects for each language:
 78 | ```
 79 | test/resources/repos/
 80 | ├── csharp/test_repo/
 81 | │   ├── serena.sln
 82 | │   ├── TestProject.csproj
 83 | │   ├── Program.cs
 84 | │   └── Models/Person.cs
 85 | ├── python/test_repo/
 86 | ├── typescript/test_repo/
 87 | └── ...
 88 | ```
 89 | 
 90 | ### Test Infrastructure
 91 | - **`test/conftest.py`** - Shared test fixtures and utilities
 92 | - **`create_ls()`** function - Creates language server instances for testing
 93 | - **`language_server` fixture** - Parametrized fixture for multi-language tests
 94 | 
 95 | ## Key Configuration Files
 96 | 
 97 | - **`pyproject.toml`** - Python dependencies, build config, and tool settings
 98 | - **`.serena/`** directories - Project-specific Serena configuration and memories
 99 | - **`CLAUDE.md`** - Instructions for AI assistants working on the project
100 | 
101 | ## Dependencies Management
102 | 
103 | The project uses modern Python tooling:
104 | - **uv** for fast dependency resolution and virtual environments
105 | - **pytest** for testing with language-specific markers (`@pytest.mark.csharp`)
106 | - **ruff** for linting and formatting
107 | - **mypy** for type checking
108 | 
109 | ## Build and Development
110 | 
111 | - **Docker support** - Full containerized development environment
112 | - **GitHub Actions** - CI/CD with language server testing
113 | - **Development scripts** in `scripts/` directory
```

--------------------------------------------------------------------------------
/src/solidlsp/language_servers/omnisharp/workspace_did_change_configuration.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |     "RoslynExtensionsOptions": {
  3 |         "EnableDecompilationSupport": false,
  4 |         "EnableAnalyzersSupport": true,
  5 |         "EnableImportCompletion": true,
  6 |         "EnableAsyncCompletion": false,
  7 |         "DocumentAnalysisTimeoutMs": 30000,
  8 |         "DiagnosticWorkersThreadCount": 18,
  9 |         "AnalyzeOpenDocumentsOnly": true,
 10 |         "InlayHintsOptions": {
 11 |             "EnableForParameters": false,
 12 |             "ForLiteralParameters": false,
 13 |             "ForIndexerParameters": false,
 14 |             "ForObjectCreationParameters": false,
 15 |             "ForOtherParameters": false,
 16 |             "SuppressForParametersThatDifferOnlyBySuffix": false,
 17 |             "SuppressForParametersThatMatchMethodIntent": false,
 18 |             "SuppressForParametersThatMatchArgumentName": false,
 19 |             "EnableForTypes": false,
 20 |             "ForImplicitVariableTypes": false,
 21 |             "ForLambdaParameterTypes": false,
 22 |             "ForImplicitObjectCreation": false
 23 |         },
 24 |         "LocationPaths": null
 25 |     },
 26 |     "FormattingOptions": {
 27 |         "OrganizeImports": false,
 28 |         "EnableEditorConfigSupport": true,
 29 |         "NewLine": "\n",
 30 |         "UseTabs": false,
 31 |         "TabSize": 4,
 32 |         "IndentationSize": 4,
 33 |         "SpacingAfterMethodDeclarationName": false,
 34 |         "SeparateImportDirectiveGroups": false,
 35 |         "SpaceWithinMethodDeclarationParenthesis": false,
 36 |         "SpaceBetweenEmptyMethodDeclarationParentheses": false,
 37 |         "SpaceAfterMethodCallName": false,
 38 |         "SpaceWithinMethodCallParentheses": false,
 39 |         "SpaceBetweenEmptyMethodCallParentheses": false,
 40 |         "SpaceAfterControlFlowStatementKeyword": true,
 41 |         "SpaceWithinExpressionParentheses": false,
 42 |         "SpaceWithinCastParentheses": false,
 43 |         "SpaceWithinOtherParentheses": false,
 44 |         "SpaceAfterCast": false,
 45 |         "SpaceBeforeOpenSquareBracket": false,
 46 |         "SpaceBetweenEmptySquareBrackets": false,
 47 |         "SpaceWithinSquareBrackets": false,
 48 |         "SpaceAfterColonInBaseTypeDeclaration": true,
 49 |         "SpaceAfterComma": true,
 50 |         "SpaceAfterDot": false,
 51 |         "SpaceAfterSemicolonsInForStatement": true,
 52 |         "SpaceBeforeColonInBaseTypeDeclaration": true,
 53 |         "SpaceBeforeComma": false,
 54 |         "SpaceBeforeDot": false,
 55 |         "SpaceBeforeSemicolonsInForStatement": false,
 56 |         "SpacingAroundBinaryOperator": "single",
 57 |         "IndentBraces": false,
 58 |         "IndentBlock": true,
 59 |         "IndentSwitchSection": true,
 60 |         "IndentSwitchCaseSection": true,
 61 |         "IndentSwitchCaseSectionWhenBlock": true,
 62 |         "LabelPositioning": "oneLess",
 63 |         "WrappingPreserveSingleLine": true,
 64 |         "WrappingKeepStatementsOnSingleLine": true,
 65 |         "NewLinesForBracesInTypes": true,
 66 |         "NewLinesForBracesInMethods": true,
 67 |         "NewLinesForBracesInProperties": true,
 68 |         "NewLinesForBracesInAccessors": true,
 69 |         "NewLinesForBracesInAnonymousMethods": true,
 70 |         "NewLinesForBracesInControlBlocks": true,
 71 |         "NewLinesForBracesInAnonymousTypes": true,
 72 |         "NewLinesForBracesInObjectCollectionArrayInitializers": true,
 73 |         "NewLinesForBracesInLambdaExpressionBody": true,
 74 |         "NewLineForElse": true,
 75 |         "NewLineForCatch": true,
 76 |         "NewLineForFinally": true,
 77 |         "NewLineForMembersInObjectInit": true,
 78 |         "NewLineForMembersInAnonymousTypes": true,
 79 |         "NewLineForClausesInQuery": true
 80 |     },
 81 |     "FileOptions": {
 82 |         "SystemExcludeSearchPatterns": [
 83 |             "**/node_modules/**/*",
 84 |             "**/bin/**/*",
 85 |             "**/obj/**/*",
 86 |             "**/.git/**/*",
 87 |             "**/.git",
 88 |             "**/.svn",
 89 |             "**/.hg",
 90 |             "**/CVS",
 91 |             "**/.DS_Store",
 92 |             "**/Thumbs.db"
 93 |         ],
 94 |         "ExcludeSearchPatterns": []
 95 |     },
 96 |     "RenameOptions": {
 97 |         "RenameOverloads": false,
 98 |         "RenameInStrings": false,
 99 |         "RenameInComments": false
100 |     },
101 |     "ImplementTypeOptions": {
102 |         "InsertionBehavior": 0,
103 |         "PropertyGenerationBehavior": 0
104 |     },
105 |     "DotNetCliOptions": {
106 |         "LocationPaths": null
107 |     },
108 |     "Plugins": {
109 |         "LocationPaths": null
110 |     }
111 | }
```

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

```
  1 | # Utility functions for PowerShell operations
  2 | 
  3 | <#
  4 | .SYNOPSIS
  5 |     Converts a string to uppercase.
  6 | .PARAMETER InputString
  7 |     The string to convert.
  8 | .OUTPUTS
  9 |     System.String - The uppercase string.
 10 | #>
 11 | function Convert-ToUpperCase {
 12 |     [CmdletBinding()]
 13 |     [OutputType([string])]
 14 |     param(
 15 |         [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
 16 |         [string]$InputString
 17 |     )
 18 | 
 19 |     return $InputString.ToUpper()
 20 | }
 21 | 
 22 | <#
 23 | .SYNOPSIS
 24 |     Converts a string to lowercase.
 25 | .PARAMETER InputString
 26 |     The string to convert.
 27 | .OUTPUTS
 28 |     System.String - The lowercase string.
 29 | #>
 30 | function Convert-ToLowerCase {
 31 |     [CmdletBinding()]
 32 |     [OutputType([string])]
 33 |     param(
 34 |         [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
 35 |         [string]$InputString
 36 |     )
 37 | 
 38 |     return $InputString.ToLower()
 39 | }
 40 | 
 41 | <#
 42 | .SYNOPSIS
 43 |     Removes leading and trailing whitespace from a string.
 44 | .PARAMETER InputString
 45 |     The string to trim.
 46 | .OUTPUTS
 47 |     System.String - The trimmed string.
 48 | #>
 49 | function Remove-Whitespace {
 50 |     [CmdletBinding()]
 51 |     [OutputType([string])]
 52 |     param(
 53 |         [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
 54 |         [string]$InputString
 55 |     )
 56 | 
 57 |     return $InputString.Trim()
 58 | }
 59 | 
 60 | <#
 61 | .SYNOPSIS
 62 |     Creates a backup of a file.
 63 | .PARAMETER FilePath
 64 |     The path to the file to backup.
 65 | .PARAMETER BackupDirectory
 66 |     The directory where the backup will be created.
 67 | .OUTPUTS
 68 |     System.String - The path to the backup file.
 69 | #>
 70 | function Backup-File {
 71 |     [CmdletBinding()]
 72 |     [OutputType([string])]
 73 |     param(
 74 |         [Parameter(Mandatory = $true)]
 75 |         [string]$FilePath,
 76 | 
 77 |         [Parameter(Mandatory = $false)]
 78 |         [string]$BackupDirectory = "."
 79 |     )
 80 | 
 81 |     if (-not (Test-Path $FilePath)) {
 82 |         throw "File not found: $FilePath"
 83 |     }
 84 | 
 85 |     $fileName = Split-Path $FilePath -Leaf
 86 |     $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
 87 |     $backupName = "$fileName.$timestamp.bak"
 88 |     $backupPath = Join-Path $BackupDirectory $backupName
 89 | 
 90 |     Copy-Item -Path $FilePath -Destination $backupPath
 91 |     return $backupPath
 92 | }
 93 | 
 94 | <#
 95 | .SYNOPSIS
 96 |     Checks if an array contains a specific element.
 97 | .PARAMETER Array
 98 |     The array to search.
 99 | .PARAMETER Element
100 |     The element to find.
101 | .OUTPUTS
102 |     System.Boolean - True if the element is found, false otherwise.
103 | #>
104 | function Test-ArrayContains {
105 |     [CmdletBinding()]
106 |     [OutputType([bool])]
107 |     param(
108 |         [Parameter(Mandatory = $true)]
109 |         [array]$Array,
110 | 
111 |         [Parameter(Mandatory = $true)]
112 |         $Element
113 |     )
114 | 
115 |     return $Array -contains $Element
116 | }
117 | 
118 | <#
119 | .SYNOPSIS
120 |     Writes a log message with timestamp.
121 | .PARAMETER Message
122 |     The message to log.
123 | .PARAMETER Level
124 |     The log level (Info, Warning, Error).
125 | #>
126 | function Write-LogMessage {
127 |     [CmdletBinding()]
128 |     param(
129 |         [Parameter(Mandatory = $true)]
130 |         [string]$Message,
131 | 
132 |         [Parameter(Mandatory = $false)]
133 |         [ValidateSet("Info", "Warning", "Error")]
134 |         [string]$Level = "Info"
135 |     )
136 | 
137 |     $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
138 |     $logEntry = "[$timestamp] [$Level] $Message"
139 | 
140 |     switch ($Level) {
141 |         "Info" { Write-Host $logEntry -ForegroundColor White }
142 |         "Warning" { Write-Host $logEntry -ForegroundColor Yellow }
143 |         "Error" { Write-Host $logEntry -ForegroundColor Red }
144 |     }
145 | }
146 | 
147 | <#
148 | .SYNOPSIS
149 |     Validates if a string is a valid email address.
150 | .PARAMETER Email
151 |     The email address to validate.
152 | .OUTPUTS
153 |     System.Boolean - True if the email is valid, false otherwise.
154 | #>
155 | function Test-ValidEmail {
156 |     [CmdletBinding()]
157 |     [OutputType([bool])]
158 |     param(
159 |         [Parameter(Mandatory = $true)]
160 |         [string]$Email
161 |     )
162 | 
163 |     $emailRegex = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
164 |     return $Email -match $emailRegex
165 | }
166 | 
167 | <#
168 | .SYNOPSIS
169 |     Checks if a string is a valid number.
170 | .PARAMETER Value
171 |     The string to check.
172 | .OUTPUTS
173 |     System.Boolean - True if the string is a valid number, false otherwise.
174 | #>
175 | function Test-IsNumber {
176 |     [CmdletBinding()]
177 |     [OutputType([bool])]
178 |     param(
179 |         [Parameter(Mandatory = $true)]
180 |         [string]$Value
181 |     )
182 | 
183 |     $number = 0
184 |     return [double]::TryParse($Value, [ref]$number)
185 | }
186 | 
```

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

```vue
  1 | <script setup lang="ts">
  2 | import { computed, ref } from 'vue'
  3 | 
  4 | /**
  5 |  * Props interface for CalculatorButton.
  6 |  * Demonstrates: defineProps with TypeScript interface
  7 |  */
  8 | interface Props {
  9 |   label: string | number
 10 |   variant?: 'digit' | 'operation' | 'equals' | 'clear'
 11 |   disabled?: boolean
 12 |   active?: boolean
 13 |   size?: 'small' | 'medium' | 'large'
 14 | }
 15 | 
 16 | /**
 17 |  * Emits interface for CalculatorButton.
 18 |  * Demonstrates: defineEmits with TypeScript
 19 |  */
 20 | interface Emits {
 21 |   click: [value: string | number]
 22 |   hover: [isHovering: boolean]
 23 |   focus: []
 24 |   blur: []
 25 | }
 26 | 
 27 | // Define props with defaults
 28 | const props = withDefaults(defineProps<Props>(), {
 29 |   variant: 'digit',
 30 |   disabled: false,
 31 |   active: false,
 32 |   size: 'medium'
 33 | })
 34 | 
 35 | // Define emits
 36 | const emit = defineEmits<Emits>()
 37 | 
 38 | // Local state
 39 | const isHovered = ref(false)
 40 | const isFocused = ref(false)
 41 | const pressCount = ref(0)
 42 | 
 43 | // Computed classes based on props and state
 44 | const buttonClass = computed(() => {
 45 |   const classes = ['calc-button', `calc-button--${props.variant}`, `calc-button--${props.size}`]
 46 | 
 47 |   if (props.active) classes.push('calc-button--active')
 48 |   if (props.disabled) classes.push('calc-button--disabled')
 49 |   if (isHovered.value) classes.push('calc-button--hovered')
 50 |   if (isFocused.value) classes.push('calc-button--focused')
 51 | 
 52 |   return classes.join(' ')
 53 | })
 54 | 
 55 | // Computed aria label for accessibility
 56 | const ariaLabel = computed(() => {
 57 |   const variantText = {
 58 |     digit: 'Number',
 59 |     operation: 'Operation',
 60 |     equals: 'Equals',
 61 |     clear: 'Clear'
 62 |   }[props.variant]
 63 | 
 64 |   return `${variantText}: ${props.label}`
 65 | })
 66 | 
 67 | // Event handlers that emit events
 68 | const handleClick = () => {
 69 |   if (!props.disabled) {
 70 |     pressCount.value++
 71 |     emit('click', props.label)
 72 |   }
 73 | }
 74 | 
 75 | const handleMouseEnter = () => {
 76 |   isHovered.value = true
 77 |   emit('hover', true)
 78 | }
 79 | 
 80 | const handleMouseLeave = () => {
 81 |   isHovered.value = false
 82 |   emit('hover', false)
 83 | }
 84 | 
 85 | const handleFocus = () => {
 86 |   isFocused.value = true
 87 |   emit('focus')
 88 | }
 89 | 
 90 | const handleBlur = () => {
 91 |   isFocused.value = false
 92 |   emit('blur')
 93 | }
 94 | 
 95 | // Expose internal state for parent access via template refs
 96 | // Demonstrates: defineExpose
 97 | defineExpose({
 98 |   pressCount,
 99 |   isHovered,
100 |   isFocused,
101 |   simulateClick: handleClick
102 | })
103 | </script>
104 | 
105 | <template>
106 |   <button
107 |     :class="buttonClass"
108 |     :disabled="disabled"
109 |     :aria-label="ariaLabel"
110 |     @click="handleClick"
111 |     @mouseenter="handleMouseEnter"
112 |     @mouseleave="handleMouseLeave"
113 |     @focus="handleFocus"
114 |     @blur="handleBlur"
115 |   >
116 |     <span class="calc-button__label">{{ label }}</span>
117 |     <span v-if="pressCount > 0" class="calc-button__badge">{{ pressCount }}</span>
118 |   </button>
119 | </template>
120 | 
121 | <style scoped>
122 | .calc-button {
123 |   position: relative;
124 |   padding: 1rem;
125 |   font-size: 1.2rem;
126 |   border: none;
127 |   border-radius: 4px;
128 |   cursor: pointer;
129 |   transition: all 0.2s;
130 |   font-weight: 500;
131 | }
132 | 
133 | .calc-button--small {
134 |   padding: 0.5rem;
135 |   font-size: 1rem;
136 | }
137 | 
138 | .calc-button--medium {
139 |   padding: 1rem;
140 |   font-size: 1.2rem;
141 | }
142 | 
143 | .calc-button--large {
144 |   padding: 1.5rem;
145 |   font-size: 1.5rem;
146 | }
147 | 
148 | .calc-button--digit {
149 |   background: white;
150 |   color: #333;
151 | }
152 | 
153 | .calc-button--digit:hover:not(:disabled) {
154 |   background: #e0e0e0;
155 | }
156 | 
157 | .calc-button--operation {
158 |   background: #2196f3;
159 |   color: white;
160 | }
161 | 
162 | .calc-button--operation:hover:not(:disabled) {
163 |   background: #1976d2;
164 | }
165 | 
166 | .calc-button--operation.calc-button--active {
167 |   background: #1565c0;
168 | }
169 | 
170 | .calc-button--equals {
171 |   background: #4caf50;
172 |   color: white;
173 | }
174 | 
175 | .calc-button--equals:hover:not(:disabled) {
176 |   background: #45a049;
177 | }
178 | 
179 | .calc-button--clear {
180 |   background: #f44336;
181 |   color: white;
182 | }
183 | 
184 | .calc-button--clear:hover:not(:disabled) {
185 |   background: #da190b;
186 | }
187 | 
188 | .calc-button--disabled {
189 |   opacity: 0.5;
190 |   cursor: not-allowed;
191 | }
192 | 
193 | .calc-button--hovered {
194 |   transform: translateY(-2px);
195 |   box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
196 | }
197 | 
198 | .calc-button--focused {
199 |   outline: 2px solid #2196f3;
200 |   outline-offset: 2px;
201 | }
202 | 
203 | .calc-button__label {
204 |   display: block;
205 | }
206 | 
207 | .calc-button__badge {
208 |   position: absolute;
209 |   top: -5px;
210 |   right: -5px;
211 |   background: #ff5722;
212 |   color: white;
213 |   border-radius: 50%;
214 |   width: 20px;
215 |   height: 20px;
216 |   font-size: 0.7rem;
217 |   display: flex;
218 |   align-items: center;
219 |   justify-content: center;
220 | }
221 | </style>
222 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/perl/test_perl_basic.py:
--------------------------------------------------------------------------------

```python
 1 | import platform
 2 | from pathlib import Path
 3 | 
 4 | import pytest
 5 | 
 6 | from solidlsp import SolidLanguageServer
 7 | from solidlsp.ls_config import Language
 8 | 
 9 | 
10 | @pytest.mark.perl
11 | @pytest.mark.skipif(platform.system() == "Windows", reason="Perl::LanguageServer does not support native Windows operation")
12 | class TestPerlLanguageServer:
13 |     """
14 |     Tests for Perl::LanguageServer integration.
15 | 
16 |     Perl::LanguageServer provides comprehensive LSP support for Perl including:
17 |     - Document symbols (functions, variables)
18 |     - Go to definition (including cross-file)
19 |     - Find references (including cross-file) - this was not available in PLS
20 |     """
21 | 
22 |     @pytest.mark.parametrize("language_server", [Language.PERL], indirect=True)
23 |     @pytest.mark.parametrize("repo_path", [Language.PERL], indirect=True)
24 |     def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
25 |         """Test that the language server starts and stops successfully."""
26 |         # The fixture already handles start and stop
27 |         assert language_server.is_running()
28 |         assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()
29 | 
30 |     @pytest.mark.parametrize("language_server", [Language.PERL], indirect=True)
31 |     def test_document_symbols(self, language_server: SolidLanguageServer) -> None:
32 |         """Test that document symbols are correctly identified."""
33 |         # Request document symbols
34 |         all_symbols, _ = language_server.request_document_symbols("main.pl").get_all_symbols_and_roots()
35 | 
36 |         assert all_symbols, "Expected to find symbols in main.pl"
37 |         assert len(all_symbols) > 0, "Expected at least one symbol"
38 | 
39 |         # DEBUG: Print all symbols
40 |         print("\n=== All symbols in main.pl ===")
41 |         for s in all_symbols:
42 |             line = s.get("range", {}).get("start", {}).get("line", "?")
43 |             print(f"Line {line}: {s.get('name')} (kind={s.get('kind')})")
44 | 
45 |         # Check that we can find function symbols
46 |         function_symbols = [s for s in all_symbols if s.get("kind") == 12]  # 12 = Function/Method
47 |         assert len(function_symbols) >= 2, f"Expected at least 2 functions (greet, use_helper_function), found {len(function_symbols)}"
48 | 
49 |         function_names = [s.get("name") for s in function_symbols]
50 |         assert "greet" in function_names, f"Expected 'greet' function in symbols, found: {function_names}"
51 |         assert "use_helper_function" in function_names, f"Expected 'use_helper_function' in symbols, found: {function_names}"
52 | 
53 |     # @pytest.mark.skip(reason="Perl::LanguageServer cross-file definition tracking needs configuration")
54 |     @pytest.mark.parametrize("language_server", [Language.PERL], indirect=True)
55 |     def test_find_definition_across_files(self, language_server: SolidLanguageServer) -> None:
56 |         definition_location_list = language_server.request_definition("main.pl", 17, 0)
57 | 
58 |         assert len(definition_location_list) == 1
59 |         definition_location = definition_location_list[0]
60 |         print(f"Found definition: {definition_location}")
61 |         assert definition_location["uri"].endswith("helper.pl")
62 |         assert definition_location["range"]["start"]["line"] == 4  # add method on line 2 (0-indexed 1)
63 | 
64 |     @pytest.mark.parametrize("language_server", [Language.PERL], indirect=True)
65 |     def test_find_references_across_files(self, language_server: SolidLanguageServer) -> None:
66 |         """Test finding references to a function across multiple files."""
67 |         reference_locations = language_server.request_references("helper.pl", 4, 5)
68 | 
69 |         assert len(reference_locations) >= 2, f"Expected at least 2 references to helper_function, found {len(reference_locations)}"
70 | 
71 |         main_pl_refs = [ref for ref in reference_locations if ref["uri"].endswith("main.pl")]
72 |         assert len(main_pl_refs) >= 2, f"Expected at least 2 references in main.pl, found {len(main_pl_refs)}"
73 | 
74 |         main_pl_lines = sorted([ref["range"]["start"]["line"] for ref in main_pl_refs])
75 |         assert 17 in main_pl_lines, f"Expected reference at line 18 (0-indexed 17), found: {main_pl_lines}"
76 |         assert 20 in main_pl_lines, f"Expected reference at line 21 (0-indexed 20), found: {main_pl_lines}"
77 | 
```

--------------------------------------------------------------------------------
/.serena/project.yml:
--------------------------------------------------------------------------------

```yaml
 1 | ignore_all_files_in_gitignore: true
 2 | 
 3 | # list of additional paths to ignore
 4 | # same syntax as gitignore, so you can use * and **
 5 | # Was previously called `ignored_dirs`, please update your config if you are using that.
 6 | # Added (renamed)on 2025-04-07
 7 | ignored_paths: []
 8 | 
 9 | # whether the project is in read-only mode
10 | # If set to true, all editing tools will be disabled and attempts to use them will result in an error
11 | # Added on 2025-04-18
12 | read_only: false
13 | 
14 | 
15 | # list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
16 | # Below is the complete list of tools for convenience.
17 | # To make sure you have the latest list of tools, and to view their descriptions, 
18 | # execute `uv run scripts/print_tool_overview.py`.
19 | #
20 | #  * `activate_project`: Activates a project by name.
21 | #  * `check_onboarding_performed`: Checks whether project onboarding was already performed.
22 | #  * `create_text_file`: Creates/overwrites a file in the project directory.
23 | #  * `delete_lines`: Deletes a range of lines within a file.
24 | #  * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
25 | #  * `execute_shell_command`: Executes a shell command.
26 | #  * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
27 | #  * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
28 | #  * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
29 | #  * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
30 | #  * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
31 | #  * `initial_instructions`: Gets the initial instructions for the current project.
32 | #     Should only be used in settings where the system prompt cannot be set,
33 | #     e.g. in clients you have no control over, like Claude Desktop.
34 | #  * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
35 | #  * `insert_at_line`: Inserts content at a given line in a file.
36 | #  * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
37 | #  * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
38 | #  * `list_memories`: Lists memories in Serena's project-specific memory store.
39 | #  * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
40 | #  * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
41 | #  * `read_file`: Reads a file within the project directory.
42 | #  * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
43 | #  * `remove_project`: Removes a project from the Serena configuration.
44 | #  * `replace_lines`: Replaces a range of lines within a file with new content.
45 | #  * `replace_symbol_body`: Replaces the full definition of a symbol.
46 | #  * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
47 | #  * `search_for_pattern`: Performs a search for a pattern in the project.
48 | #  * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
49 | #  * `switch_modes`: Activates modes by providing a list of their names
50 | #  * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
51 | #  * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
52 | #  * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
53 | #  * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
54 | excluded_tools: []
55 | 
56 | # initial prompt for the project. It will always be given to the LLM upon activating the project
57 | # (contrary to the memories, which are loaded on demand).
58 | initial_prompt: ""
59 | 
60 | project_name: "serena"
61 | languages:
62 | - python
63 | - typescript
64 | included_optional_tools: []
65 | encoding: utf-8
66 | 
```

--------------------------------------------------------------------------------
/src/solidlsp/util/zip.py:
--------------------------------------------------------------------------------

```python
  1 | import fnmatch
  2 | import logging
  3 | import os
  4 | import sys
  5 | import zipfile
  6 | from pathlib import Path
  7 | from typing import Optional
  8 | 
  9 | log = logging.getLogger(__name__)
 10 | 
 11 | 
 12 | class SafeZipExtractor:
 13 |     """
 14 |     A utility class for extracting ZIP archives safely.
 15 | 
 16 |     Features:
 17 |     - Handles long file paths on Windows
 18 |     - Skips files that fail to extract, continuing with the rest
 19 |     - Creates necessary directories automatically
 20 |     - Optional include/exclude pattern filters
 21 |     """
 22 | 
 23 |     def __init__(
 24 |         self,
 25 |         archive_path: Path,
 26 |         extract_dir: Path,
 27 |         verbose: bool = True,
 28 |         include_patterns: Optional[list[str]] = None,
 29 |         exclude_patterns: Optional[list[str]] = None,
 30 |     ) -> None:
 31 |         """
 32 |         Initialize the SafeZipExtractor.
 33 | 
 34 |         :param archive_path: Path to the ZIP archive file
 35 |         :param extract_dir: Directory where files will be extracted
 36 |         :param verbose: Whether to log status messages
 37 |         :param include_patterns: List of glob patterns for files to extract (None = all files)
 38 |         :param exclude_patterns: List of glob patterns for files to skip
 39 |         """
 40 |         self.archive_path = Path(archive_path)
 41 |         self.extract_dir = Path(extract_dir)
 42 |         self.verbose = verbose
 43 |         self.include_patterns = include_patterns or []
 44 |         self.exclude_patterns = exclude_patterns or []
 45 | 
 46 |     def extract_all(self) -> None:
 47 |         """
 48 |         Extract all files from the archive, skipping any that fail.
 49 |         """
 50 |         if not self.archive_path.exists():
 51 |             raise FileNotFoundError(f"Archive not found: {self.archive_path}")
 52 | 
 53 |         if self.verbose:
 54 |             log.info(f"Extracting from: {self.archive_path} to {self.extract_dir}")
 55 | 
 56 |         with zipfile.ZipFile(self.archive_path, "r") as zip_ref:
 57 |             for member in zip_ref.infolist():
 58 |                 if self._should_extract(member.filename):
 59 |                     self._extract_member(zip_ref, member)
 60 |                 elif self.verbose:
 61 |                     log.info(f"Skipped: {member.filename}")
 62 | 
 63 |     def _should_extract(self, filename: str) -> bool:
 64 |         """
 65 |         Determine whether a file should be extracted based on include/exclude patterns.
 66 | 
 67 |         :param filename: The file name from the archive
 68 |         :return: True if the file should be extracted
 69 |         """
 70 |         # If include_patterns is set, only extract if it matches at least one pattern
 71 |         if self.include_patterns:
 72 |             if not any(fnmatch.fnmatch(filename, pattern) for pattern in self.include_patterns):
 73 |                 return False
 74 | 
 75 |         # If exclude_patterns is set, skip if it matches any pattern
 76 |         if self.exclude_patterns:
 77 |             if any(fnmatch.fnmatch(filename, pattern) for pattern in self.exclude_patterns):
 78 |                 return False
 79 | 
 80 |         return True
 81 | 
 82 |     def _extract_member(self, zip_ref: zipfile.ZipFile, member: zipfile.ZipInfo) -> None:
 83 |         """
 84 |         Extract a single member from the archive with error handling.
 85 | 
 86 |         :param zip_ref: Open ZipFile object
 87 |         :param member: ZipInfo object representing the file
 88 |         """
 89 |         try:
 90 |             target_path = self.extract_dir / member.filename
 91 | 
 92 |             # Ensure directory structure exists
 93 |             target_path.parent.mkdir(parents=True, exist_ok=True)
 94 | 
 95 |             # Handle long paths on Windows
 96 |             final_path = self._normalize_path(target_path)
 97 | 
 98 |             # Extract file
 99 |             with zip_ref.open(member) as source, open(final_path, "wb") as target:
100 |                 target.write(source.read())
101 | 
102 |             if self.verbose:
103 |                 log.info(f"Extracted: {member.filename}")
104 | 
105 |         except Exception as e:
106 |             log.error(f"Failed to extract {member.filename}: {e}")
107 | 
108 |     @staticmethod
109 |     def _normalize_path(path: Path) -> Path:
110 |         """
111 |         Adjust path to handle long paths on Windows.
112 | 
113 |         :param path: Original path
114 |         :return: Normalized path
115 |         """
116 |         if sys.platform.startswith("win"):
117 |             return Path(rf"\\?\{os.path.abspath(path)}")
118 |         return path  # type: ignore
119 | 
120 | 
121 | # Example usage:
122 | # extractor = SafeZipExtractor(
123 | #     archive_path=Path("file.nupkg"),
124 | #     extract_dir=Path("extract_dir"),
125 | #     include_patterns=["*.dll", "*.xml"],
126 | #     exclude_patterns=["*.pdb"]
127 | # )
128 | # extractor.extract_all()
129 | 
```

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

```yaml
 1 | language_backend: LSP
 2 | # the language backend to use for code understanding and manipulation.
 3 | # Possible values are:
 4 | #  * LSP: Use the language server protocol (LSP), spawning freely available language servers
 5 | #      via the SolidLSP library that is part of Serena.
 6 | #  * JetBrains: Use the Serena plugin in your JetBrains IDE.
 7 | #      (requires the plugin to be installed and the project being worked on to be open
 8 | #      in your IDE).
 9 | 
10 | gui_log_window: False
11 | # whether to open a graphical window with Serena's logs.
12 | # This is mainly supported on Windows and (partly) on Linux; not available on macOS.
13 | # If you prefer a browser-based tool, use the `web_dashboard` option instead.
14 | # Further information: https://oraios.github.io/serena/02-usage/060_dashboard.html
15 | #
16 | # Being able to inspect logs is useful both for troubleshooting and for monitoring the tool calls,
17 | # especially when using the agno playground, since the tool calls are not always shown,
18 | # and the input params are never shown in the agno UI.
19 | # When used as MCP server for Claude Desktop, the logs are primarily for troubleshooting.
20 | # Note: unfortunately, the various entities starting the Serena server or agent do so in
21 | # mysterious ways, often starting multiple instances of the process without shutting down
22 | # previous instances. This can lead to multiple log windows being opened, and only the last
23 | # window being updated. Since we can't control how agno or Claude Desktop start Serena,
24 | # we have to live with this limitation for now.
25 | 
26 | web_dashboard: True
27 | # whether to open the Serena web dashboard (which will be accessible through your web browser) that
28 | # provides access to Serena's configuration and state as well as the current session logs.
29 | # The web dashboard is supported on all platforms.
30 | # Further information: https://oraios.github.io/serena/02-usage/060_dashboard.html
31 | 
32 | web_dashboard_listen_address: 127.0.0.1
33 | # The address where the web dashboard will listen on
34 | 
35 | web_dashboard_open_on_launch: True
36 | # whether to open a browser window with the web dashboard when Serena starts (provided that web_dashboard
37 | # is enabled).
38 | # If set to false, you can still open the dashboard manually by
39 | # a) telling the LLM to "open the dashboard" (provided that the open_dashboard tool is enabled) or by
40 | # b) manually navigating to http://localhost:24282/dashboard/ in your web browser (actual port
41 | #    may be higher if you have multiple instances running; try ports 24283, 24284, etc.)
42 | # See also: https://oraios.github.io/serena/02-usage/060_dashboard.html
43 | 
44 | log_level: 20
45 | # the minimum log level for the GUI log window and the dashboard (10 = debug, 20 = info, 30 = warning, 40 = error)
46 | 
47 | trace_lsp_communication: False
48 | # whether to trace the communication between Serena and the language servers.
49 | # This is useful for debugging language server issues.
50 | 
51 | ls_specific_settings: {}
52 | # Added on 23.08.2025
53 | # Advanced configuration option allowing to configure language server implementation specific options. Maps the language
54 | # (same entry as in project.yml) to the options.
55 | # Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
56 | # No documentation on options means no options are available.
57 | 
58 | tool_timeout: 240
59 | # timeout, in seconds, after which tool executions are terminated
60 | 
61 | excluded_tools: []
62 | # list of tools to be globally excluded
63 | 
64 | included_optional_tools: []
65 | # list of optional tools (which are disabled by default) to be included
66 | 
67 | default_max_tool_answer_chars: 150000
68 | # Used as default for tools where the apply method has a default maximal answer length.
69 | # Even though the value of the max_answer_chars can be changed when calling the tool, it may make sense to adjust this default
70 | # through the global configuration.
71 | 
72 | token_count_estimator: CHAR_COUNT
73 | # the name of the token count estimator to use for tool usage statistics.
74 | # See the `RegisteredTokenCountEstimator` enum for available options.
75 | #
76 | # By default, a very naive character count estimator is used, which simply counts the number of characters.
77 | # You can configure this to TIKTOKEN_GPT4 to use a local tiktoken-based estimator for GPT-4 (will download tiktoken
78 | # data files on first run), or ANTHROPIC_CLAUDE_SONNET_4 which will use the (free of cost) Anthropic API to
79 | # estimate the token count using the Claude Sonnet 4 tokenizer.
80 | 
81 | # The list of registered project paths (updated automatically).
82 | projects: []
83 | 
```

--------------------------------------------------------------------------------
/lessons_learned.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Lessons Learned
 2 | 
 3 | In this document we briefly collect what we have learned while developing and using Serena,
 4 | what works well and what doesn't.
 5 | 
 6 | ## What Worked
 7 | 
 8 | ### Separate Tool Logic From MCP Implementation
 9 | 
10 | MCP is just another protocol, one should let the details of it creep into the application logic.
11 | The official docs suggest using function annotations to define tools and prompts. While that may be
12 | useful for small projects to get going fast, it is not wise for more serious projects. In Serena,
13 | all tools are defined independently and then converted to instances of `MCPTool` using our `make_tool`
14 | function.
15 | 
16 | ### Autogenerated PromptFactory
17 | 
18 | Prompt templates are central for most LLM applications, so one needs good representations of them in the code,
19 | while at the same time they often need to be customizable and exposed to users. In Serena we address these conflicting 
20 | needs by defining prompt templates (in jinja format) in separate yamls that users can easily modify and by autogenerated
21 | a `PromptFactory` class with meaningful method and parameter names from these yamls. The latter is committed to our code.
22 | We separated out the generation logic into the [interprompt](/src/interprompt/README.md) subpackage that can be used as a library.
23 | 
24 | ### Tempfiles and Snapshots for Testing of Editing Tools
25 | 
26 | We test most aspects of Serena by having a small "project" for each supported language in `tests/resources`.
27 | For the editing tools, which would change the code in these projects, we use tempfiles to copy over the code.
28 | The pretty awesome [syrupy](https://github.com/syrupy-project/syrupy) pytest plugin helped in developing
29 | snapshot tests.
30 | 
31 | ### Dashboard and GUI for Logging
32 | 
33 | It is very useful to know what the MCP Server is doing. We collect and display logs in a GUI or a web dashboard,
34 | which helps a lot in seeing what's going on and in identifying any issues.
35 | 
36 | ### Unrestricted Bash Tool
37 | 
38 | We know it's not particularly safe to permit unlimited shell commands outside a sandbox, but we did quite some
39 | evaluations and so far... nothing bad has happened. Seems like the current versions of the AI overlords rarely want to execute `sudo rm - rf /`.
40 | Still, we are working on a safer approach as well as better integration with sandboxing.
41 | 
42 | ### Multilspy
43 | 
44 | The [multilspy](https://github.com/microsoft/multilspy/) project helped us a lot in getting started and stands at the core of Serena.
45 | Many more well known python implementations of language servers were subpar in code quality and design (for example, missing types).
46 | 
47 | ### Developing Serena with Serena
48 | 
49 | We clearly notice that the better the tool gets, the easier it is to make it even better
50 | 
51 | ## Prompting
52 | 
53 | ### Shouting and Emotive Language May Be Needed
54 | 
55 | When developing the `ReplaceRegexTool` we were initially not able to make Claude 4 (in Claude Desktop) use wildcards to save on output tokens. Neither
56 | examples nor explicit instructions helped. It was only after adding 
57 | 
58 | ```
59 | IMPORTANT: REMEMBER TO USE WILDCARDS WHEN APPROPRIATE! I WILL BE VERY UNHAPPY IF YOU WRITE LONG REGEXES WITHOUT USING WILDCARDS INSTEAD!
60 | ```
61 | 
62 | to the initial instructions and to the tool description that Claude finally started following the instructions.
63 | 
64 | ## What Didn't Work
65 | 
66 | ### Lifespan Handling by MCP Clients
67 | 
68 | The MCP technology is clearly very green. Even though there is a lifespan context in the MCP SDK,
69 | many clients, including Claude Desktop, fail to properly clean up, leaving zombie processes behind.
70 | We mitigate this through the GUI window and the dashboard, so the user sees whether Serena is running
71 | and can terminate it there.
72 | 
73 | ### Trusting Asyncio
74 | 
75 | Running multiple asyncio apps led to non-deterministic 
76 | event loop contamination and deadlocks, which were very hard to debug
77 | and understand. We solved this with a large hammer, by putting all asyncio apps into a separate
78 | process. It made the code much more complex and slightly enhanced RAM requirements, but it seems
79 | like that was the only way to reliably overcome asyncio deadlock issues.
80 | 
81 | ### Cross-OS Tkinter GUI
82 | 
83 | Different OS have different limitations when it comes to starting a window or dealing with Tkinter
84 | installations. This was so messy to get right that we pivoted to a web-dashboard instead
85 | 
86 | ### Editing Based on Line Numbers
87 | 
88 | Not only are LLMs notoriously bad in counting, but also the line numbers change after edit operations,
89 | and LLMs are also often too dumb to understand that they should update the line numbers information they had
90 | received before. We pivoted to string-matching and symbol-name based editing.
```

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

```vue
  1 | <script setup lang="ts">
  2 | import { computed, ref } from 'vue'
  3 | import { storeToRefs } from 'pinia'
  4 | import { useCalculatorStore } from '@/stores/calculator'
  5 | import type { HistoryEntry } from '@/types'
  6 | 
  7 | // Get the calculator store
  8 | const store = useCalculatorStore()
  9 | 
 10 | // Use storeToRefs to get reactive references to store state
 11 | const { recentHistory, hasHistory, currentValue, operation } = storeToRefs(store)
 12 | 
 13 | // Local ref for display options
 14 | const showFullHistory = ref(false)
 15 | const maxHistoryItems = ref(5)
 16 | 
 17 | // Computed property for filtered history
 18 | const displayedHistory = computed((): HistoryEntry[] => {
 19 |   if (showFullHistory.value) {
 20 |     return recentHistory.value
 21 |   }
 22 |   return recentHistory.value.slice(0, maxHistoryItems.value)
 23 | })
 24 | 
 25 | // Format date for display
 26 | const formatDate = (date: Date): string => {
 27 |   return new Date(date).toLocaleTimeString()
 28 | }
 29 | 
 30 | // Format the current calculation status
 31 | const currentCalculation = computed((): string => {
 32 |   if (operation.value && store.previousValue !== null) {
 33 |     const opSymbol = {
 34 |       add: '+',
 35 |       subtract: '-',
 36 |       multiply: '×',
 37 |       divide: '÷'
 38 |     }[operation.value]
 39 |     return `${store.previousValue} ${opSymbol} ${currentValue.value}`
 40 |   }
 41 |   return currentValue.value.toString()
 42 | })
 43 | 
 44 | // Toggle history view
 45 | const toggleHistoryView = () => {
 46 |   showFullHistory.value = !showFullHistory.value
 47 | }
 48 | 
 49 | // Clear history handler
 50 | const clearHistory = () => {
 51 |   store.clearHistory()
 52 | }
 53 | 
 54 | // Check if history is empty
 55 | const isHistoryEmpty = computed((): boolean => {
 56 |   return !hasHistory.value
 57 | })
 58 | </script>
 59 | 
 60 | <template>
 61 |   <div class="calculator-display">
 62 |     <div class="current-calculation">
 63 |       <h3>Current Calculation</h3>
 64 |       <div class="calculation-value">
 65 |         {{ currentCalculation }}
 66 |       </div>
 67 |     </div>
 68 | 
 69 |     <div class="history-section">
 70 |       <div class="history-header">
 71 |         <h3>History</h3>
 72 |         <button
 73 |           v-if="hasHistory"
 74 |           @click="toggleHistoryView"
 75 |           class="btn-toggle"
 76 |         >
 77 |           {{ showFullHistory ? 'Show Less' : 'Show All' }}
 78 |         </button>
 79 |         <button
 80 |           v-if="hasHistory"
 81 |           @click="clearHistory"
 82 |           class="btn-clear-history"
 83 |         >
 84 |           Clear History
 85 |         </button>
 86 |       </div>
 87 | 
 88 |       <div v-if="isHistoryEmpty" class="empty-history">
 89 |         No calculations yet
 90 |       </div>
 91 | 
 92 |       <div v-else class="history-list">
 93 |         <div
 94 |           v-for="(entry, index) in displayedHistory"
 95 |           :key="`${entry.timestamp}-${index}`"
 96 |           class="history-item"
 97 |         >
 98 |           <span class="expression">{{ entry.expression }}</span>
 99 |           <span class="result">= {{ entry.result }}</span>
100 |           <span class="timestamp">{{ formatDate(entry.timestamp) }}</span>
101 |         </div>
102 |       </div>
103 | 
104 |       <div v-if="!showFullHistory && recentHistory.length > maxHistoryItems" class="history-count">
105 |         Showing {{ maxHistoryItems }} of {{ recentHistory.length }} entries
106 |       </div>
107 |     </div>
108 |   </div>
109 | </template>
110 | 
111 | <style scoped>
112 | .calculator-display {
113 |   display: flex;
114 |   flex-direction: column;
115 |   gap: 1.5rem;
116 |   padding: 1rem;
117 |   background: white;
118 |   border-radius: 8px;
119 |   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
120 | }
121 | 
122 | .current-calculation {
123 |   padding: 1rem;
124 |   background: #e3f2fd;
125 |   border-radius: 4px;
126 | }
127 | 
128 | .current-calculation h3 {
129 |   margin: 0 0 0.5rem 0;
130 |   font-size: 1rem;
131 |   color: #1976d2;
132 | }
133 | 
134 | .calculation-value {
135 |   font-size: 1.5rem;
136 |   font-weight: bold;
137 |   color: #333;
138 | }
139 | 
140 | .history-section {
141 |   display: flex;
142 |   flex-direction: column;
143 |   gap: 1rem;
144 | }
145 | 
146 | .history-header {
147 |   display: flex;
148 |   align-items: center;
149 |   gap: 1rem;
150 | }
151 | 
152 | .history-header h3 {
153 |   margin: 0;
154 |   flex: 1;
155 | }
156 | 
157 | .btn-toggle,
158 | .btn-clear-history {
159 |   padding: 0.5rem 1rem;
160 |   border: none;
161 |   border-radius: 4px;
162 |   cursor: pointer;
163 |   font-size: 0.9rem;
164 | }
165 | 
166 | .btn-toggle {
167 |   background: #2196f3;
168 |   color: white;
169 | }
170 | 
171 | .btn-toggle:hover {
172 |   background: #1976d2;
173 | }
174 | 
175 | .btn-clear-history {
176 |   background: #f44336;
177 |   color: white;
178 | }
179 | 
180 | .btn-clear-history:hover {
181 |   background: #da190b;
182 | }
183 | 
184 | .empty-history {
185 |   padding: 2rem;
186 |   text-align: center;
187 |   color: #999;
188 |   font-style: italic;
189 | }
190 | 
191 | .history-list {
192 |   display: flex;
193 |   flex-direction: column;
194 |   gap: 0.5rem;
195 | }
196 | 
197 | .history-item {
198 |   display: grid;
199 |   grid-template-columns: 2fr 1fr 1fr;
200 |   gap: 1rem;
201 |   padding: 0.75rem;
202 |   background: #f5f5f5;
203 |   border-radius: 4px;
204 |   align-items: center;
205 | }
206 | 
207 | .expression {
208 |   font-weight: 500;
209 | }
210 | 
211 | .result {
212 |   font-weight: bold;
213 |   color: #4caf50;
214 | }
215 | 
216 | .timestamp {
217 |   font-size: 0.8rem;
218 |   color: #666;
219 |   text-align: right;
220 | }
221 | 
222 | .history-count {
223 |   text-align: center;
224 |   font-size: 0.9rem;
225 |   color: #666;
226 |   padding: 0.5rem;
227 | }
228 | </style>
229 | 
```

--------------------------------------------------------------------------------
/test/resources/repos/python/test_repo/examples/user_management.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Example demonstrating user management with the test_repo module.
  3 | 
  4 | This example showcases:
  5 | - Creating and managing users
  6 | - Using various object types and relationships
  7 | - Type annotations and complex Python patterns
  8 | """
  9 | 
 10 | import logging
 11 | from dataclasses import dataclass
 12 | from typing import Any
 13 | 
 14 | from test_repo.models import User, create_user_object
 15 | from test_repo.services import UserService
 16 | 
 17 | # Set up logging
 18 | logging.basicConfig(level=logging.INFO)
 19 | logger = logging.getLogger(__name__)
 20 | 
 21 | 
 22 | @dataclass
 23 | class UserStats:
 24 |     """Statistics about user activity."""
 25 | 
 26 |     user_id: str
 27 |     login_count: int = 0
 28 |     last_active_days: int = 0
 29 |     engagement_score: float = 0.0
 30 | 
 31 |     def is_active(self) -> bool:
 32 |         """Check if the user is considered active."""
 33 |         return self.last_active_days < 30
 34 | 
 35 | 
 36 | class UserManager:
 37 |     """Example class demonstrating complex user management."""
 38 | 
 39 |     def __init__(self, service: UserService):
 40 |         self.service = service
 41 |         self.active_users: dict[str, User] = {}
 42 |         self.user_stats: dict[str, UserStats] = {}
 43 | 
 44 |     def register_user(self, name: str, email: str, roles: list[str] | None = None) -> User:
 45 |         """Register a new user."""
 46 |         logger.info(f"Registering new user: {name} ({email})")
 47 |         user = self.service.create_user(name=name, email=email, roles=roles)
 48 |         self.active_users[user.id] = user
 49 |         self.user_stats[user.id] = UserStats(user_id=user.id)
 50 |         return user
 51 | 
 52 |     def get_user(self, user_id: str) -> User | None:
 53 |         """Get a user by ID."""
 54 |         if user_id in self.active_users:
 55 |             return self.active_users[user_id]
 56 | 
 57 |         # Try to fetch from service
 58 |         user = self.service.get_user(user_id)
 59 |         if user:
 60 |             self.active_users[user.id] = user
 61 |         return user
 62 | 
 63 |     def update_user_stats(self, user_id: str, login_count: int, days_since_active: int) -> None:
 64 |         """Update statistics for a user."""
 65 |         if user_id not in self.user_stats:
 66 |             self.user_stats[user_id] = UserStats(user_id=user_id)
 67 | 
 68 |         stats = self.user_stats[user_id]
 69 |         stats.login_count = login_count
 70 |         stats.last_active_days = days_since_active
 71 | 
 72 |         # Calculate engagement score based on activity
 73 |         engagement = (100 - min(days_since_active, 100)) * 0.8
 74 |         engagement += min(login_count, 20) * 0.2
 75 |         stats.engagement_score = engagement
 76 | 
 77 |     def get_active_users(self) -> list[User]:
 78 |         """Get all active users."""
 79 |         active_user_ids = [user_id for user_id, stats in self.user_stats.items() if stats.is_active()]
 80 |         return [self.active_users[user_id] for user_id in active_user_ids if user_id in self.active_users]
 81 | 
 82 |     def get_user_by_email(self, email: str) -> User | None:
 83 |         """Find a user by their email address."""
 84 |         for user in self.active_users.values():
 85 |             if user.email == email:
 86 |                 return user
 87 |         return None
 88 | 
 89 | 
 90 | # Example function demonstrating type annotations
 91 | def process_user_data(users: list[User], include_inactive: bool = False, transform_func: callable | None = None) -> dict[str, Any]:
 92 |     """Process user data with optional transformations."""
 93 |     result: dict[str, Any] = {"users": [], "total": 0, "admin_count": 0}
 94 | 
 95 |     for user in users:
 96 |         if transform_func:
 97 |             user_data = transform_func(user.to_dict())
 98 |         else:
 99 |             user_data = user.to_dict()
100 | 
101 |         result["users"].append(user_data)
102 |         result["total"] += 1
103 | 
104 |         if "admin" in user.roles:
105 |             result["admin_count"] += 1
106 | 
107 |     return result
108 | 
109 | 
110 | def main():
111 |     """Main function demonstrating the usage of UserManager."""
112 |     # Initialize service and manager
113 |     service = UserService()
114 |     manager = UserManager(service)
115 | 
116 |     # Register some users
117 |     admin = manager.register_user("Admin User", "[email protected]", ["admin"])
118 |     user1 = manager.register_user("Regular User", "[email protected]", ["user"])
119 |     user2 = manager.register_user("Another User", "[email protected]", ["user"])
120 | 
121 |     # Update some stats
122 |     manager.update_user_stats(admin.id, 100, 5)
123 |     manager.update_user_stats(user1.id, 50, 10)
124 |     manager.update_user_stats(user2.id, 10, 45)  # Inactive user
125 | 
126 |     # Get active users
127 |     active_users = manager.get_active_users()
128 |     logger.info(f"Active users: {len(active_users)}")
129 | 
130 |     # Process user data
131 |     user_data = process_user_data(active_users, transform_func=lambda u: {**u, "full_name": u.get("name", "")})
132 | 
133 |     logger.info(f"Processed {user_data['total']} users, {user_data['admin_count']} admins")
134 | 
135 |     # Example of calling create_user directly
136 |     external_user = create_user_object(id="ext123", name="External User", email="[email protected]", roles=["external"])
137 |     logger.info(f"Created external user: {external_user.name}")
138 | 
139 | 
140 | if __name__ == "__main__":
141 |     main()
142 | 
```

--------------------------------------------------------------------------------
/test/resources/repos/python/test_repo/ignore_this_dir_with_postfix/ignored_module.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Example demonstrating user management with the test_repo module.
  3 | 
  4 | This example showcases:
  5 | - Creating and managing users
  6 | - Using various object types and relationships
  7 | - Type annotations and complex Python patterns
  8 | """
  9 | 
 10 | import logging
 11 | from dataclasses import dataclass
 12 | from typing import Any
 13 | 
 14 | from test_repo.models import User, create_user_object
 15 | from test_repo.services import UserService
 16 | 
 17 | # Set up logging
 18 | logging.basicConfig(level=logging.INFO)
 19 | logger = logging.getLogger(__name__)
 20 | 
 21 | 
 22 | @dataclass
 23 | class UserStats:
 24 |     """Statistics about user activity."""
 25 | 
 26 |     user_id: str
 27 |     login_count: int = 0
 28 |     last_active_days: int = 0
 29 |     engagement_score: float = 0.0
 30 | 
 31 |     def is_active(self) -> bool:
 32 |         """Check if the user is considered active."""
 33 |         return self.last_active_days < 30
 34 | 
 35 | 
 36 | class UserManager:
 37 |     """Example class demonstrating complex user management."""
 38 | 
 39 |     def __init__(self, service: UserService):
 40 |         self.service = service
 41 |         self.active_users: dict[str, User] = {}
 42 |         self.user_stats: dict[str, UserStats] = {}
 43 | 
 44 |     def register_user(self, name: str, email: str, roles: list[str] | None = None) -> User:
 45 |         """Register a new user."""
 46 |         logger.info(f"Registering new user: {name} ({email})")
 47 |         user = self.service.create_user(name=name, email=email, roles=roles)
 48 |         self.active_users[user.id] = user
 49 |         self.user_stats[user.id] = UserStats(user_id=user.id)
 50 |         return user
 51 | 
 52 |     def get_user(self, user_id: str) -> User | None:
 53 |         """Get a user by ID."""
 54 |         if user_id in self.active_users:
 55 |             return self.active_users[user_id]
 56 | 
 57 |         # Try to fetch from service
 58 |         user = self.service.get_user(user_id)
 59 |         if user:
 60 |             self.active_users[user.id] = user
 61 |         return user
 62 | 
 63 |     def update_user_stats(self, user_id: str, login_count: int, days_since_active: int) -> None:
 64 |         """Update statistics for a user."""
 65 |         if user_id not in self.user_stats:
 66 |             self.user_stats[user_id] = UserStats(user_id=user_id)
 67 | 
 68 |         stats = self.user_stats[user_id]
 69 |         stats.login_count = login_count
 70 |         stats.last_active_days = days_since_active
 71 | 
 72 |         # Calculate engagement score based on activity
 73 |         engagement = (100 - min(days_since_active, 100)) * 0.8
 74 |         engagement += min(login_count, 20) * 0.2
 75 |         stats.engagement_score = engagement
 76 | 
 77 |     def get_active_users(self) -> list[User]:
 78 |         """Get all active users."""
 79 |         active_user_ids = [user_id for user_id, stats in self.user_stats.items() if stats.is_active()]
 80 |         return [self.active_users[user_id] for user_id in active_user_ids if user_id in self.active_users]
 81 | 
 82 |     def get_user_by_email(self, email: str) -> User | None:
 83 |         """Find a user by their email address."""
 84 |         for user in self.active_users.values():
 85 |             if user.email == email:
 86 |                 return user
 87 |         return None
 88 | 
 89 | 
 90 | # Example function demonstrating type annotations
 91 | def process_user_data(users: list[User], include_inactive: bool = False, transform_func: callable | None = None) -> dict[str, Any]:
 92 |     """Process user data with optional transformations."""
 93 |     result: dict[str, Any] = {"users": [], "total": 0, "admin_count": 0}
 94 | 
 95 |     for user in users:
 96 |         if transform_func:
 97 |             user_data = transform_func(user.to_dict())
 98 |         else:
 99 |             user_data = user.to_dict()
100 | 
101 |         result["users"].append(user_data)
102 |         result["total"] += 1
103 | 
104 |         if "admin" in user.roles:
105 |             result["admin_count"] += 1
106 | 
107 |     return result
108 | 
109 | 
110 | def main():
111 |     """Main function demonstrating the usage of UserManager."""
112 |     # Initialize service and manager
113 |     service = UserService()
114 |     manager = UserManager(service)
115 | 
116 |     # Register some users
117 |     admin = manager.register_user("Admin User", "[email protected]", ["admin"])
118 |     user1 = manager.register_user("Regular User", "[email protected]", ["user"])
119 |     user2 = manager.register_user("Another User", "[email protected]", ["user"])
120 | 
121 |     # Update some stats
122 |     manager.update_user_stats(admin.id, 100, 5)
123 |     manager.update_user_stats(user1.id, 50, 10)
124 |     manager.update_user_stats(user2.id, 10, 45)  # Inactive user
125 | 
126 |     # Get active users
127 |     active_users = manager.get_active_users()
128 |     logger.info(f"Active users: {len(active_users)}")
129 | 
130 |     # Process user data
131 |     user_data = process_user_data(active_users, transform_func=lambda u: {**u, "full_name": u.get("name", "")})
132 | 
133 |     logger.info(f"Processed {user_data['total']} users, {user_data['admin_count']} admins")
134 | 
135 |     # Example of calling create_user directly
136 |     external_user = create_user_object(id="ext123", name="External User", email="[email protected]", roles=["external"])
137 |     logger.info(f"Created external user: {external_user.name}")
138 | 
139 | 
140 | if __name__ == "__main__":
141 |     main()
142 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/r/test_r_basic.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Basic tests for R Language Server integration
 3 | """
 4 | 
 5 | import os
 6 | from pathlib import Path
 7 | 
 8 | import pytest
 9 | 
10 | from solidlsp import SolidLanguageServer
11 | from solidlsp.ls_config import Language
12 | 
13 | 
14 | @pytest.mark.r
15 | class TestRLanguageServer:
16 |     """Test basic functionality of the R language server."""
17 | 
18 |     @pytest.mark.parametrize("language_server", [Language.R], indirect=True)
19 |     @pytest.mark.parametrize("repo_path", [Language.R], indirect=True)
20 |     def test_server_initialization(self, language_server: SolidLanguageServer, repo_path: Path):
21 |         """Test that the R language server initializes properly."""
22 |         assert language_server is not None
23 |         assert language_server.language_id == "r"
24 |         assert language_server.is_running()
25 |         assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()
26 | 
27 |     @pytest.mark.parametrize("language_server", [Language.R], indirect=True)
28 |     def test_symbol_retrieval(self, language_server: SolidLanguageServer):
29 |         """Test R document symbol extraction."""
30 |         all_symbols, _root_symbols = language_server.request_document_symbols(os.path.join("R", "utils.R")).get_all_symbols_and_roots()
31 | 
32 |         # Should find the three exported functions
33 |         function_symbols = [s for s in all_symbols if s.get("kind") == 12]  # Function kind
34 |         assert len(function_symbols) >= 3
35 | 
36 |         # Check that we found the expected functions
37 |         function_names = {s.get("name") for s in function_symbols}
38 |         expected_functions = {"calculate_mean", "process_data", "create_data_frame"}
39 |         assert expected_functions.issubset(function_names), f"Expected functions {expected_functions} but found {function_names}"
40 | 
41 |     @pytest.mark.parametrize("language_server", [Language.R], indirect=True)
42 |     def test_find_definition_across_files(self, language_server: SolidLanguageServer):
43 |         """Test finding function definitions across files."""
44 |         analysis_file = os.path.join("examples", "analysis.R")
45 | 
46 |         # In analysis.R line 7: create_data_frame(n = 50)
47 |         # The function create_data_frame is defined in R/utils.R
48 |         # Find definition of create_data_frame function call (0-indexed: line 6)
49 |         definition_location_list = language_server.request_definition(analysis_file, 6, 17)  # cursor on 'create_data_frame'
50 | 
51 |         assert definition_location_list, f"Expected non-empty definition_location_list but got {definition_location_list=}"
52 |         assert len(definition_location_list) >= 1
53 |         definition_location = definition_location_list[0]
54 |         assert definition_location["uri"].endswith("utils.R")
55 |         # Definition should be around line 37 (0-indexed: 36) where create_data_frame is defined
56 |         assert definition_location["range"]["start"]["line"] >= 35
57 | 
58 |     @pytest.mark.parametrize("language_server", [Language.R], indirect=True)
59 |     def test_find_references_across_files(self, language_server: SolidLanguageServer):
60 |         """Test finding function references across files."""
61 |         analysis_file = os.path.join("examples", "analysis.R")
62 | 
63 |         # Test from usage side: find references to calculate_mean from its usage in analysis.R
64 |         # In analysis.R line 13: calculate_mean(clean_data$value)
65 |         # calculate_mean function call is at line 13 (0-indexed: line 12)
66 |         references = language_server.request_references(analysis_file, 12, 15)  # cursor on 'calculate_mean'
67 | 
68 |         assert references, f"Expected non-empty references for calculate_mean but got {references=}"
69 | 
70 |         # Must find the definition in utils.R (cross-file reference)
71 |         reference_files = [ref["uri"] for ref in references]
72 |         assert any(uri.endswith("utils.R") for uri in reference_files), "Cross-file reference to definition in utils.R not found"
73 | 
74 |         # Verify we actually found the right location in utils.R
75 |         utils_refs = [ref for ref in references if ref["uri"].endswith("utils.R")]
76 |         assert len(utils_refs) >= 1, "Should find at least one reference in utils.R"
77 |         utils_ref = utils_refs[0]
78 |         # Should be around line 6 where calculate_mean is defined (0-indexed: line 5)
79 |         assert (
80 |             utils_ref["range"]["start"]["line"] == 5
81 |         ), f"Expected reference at line 5 in utils.R, got line {utils_ref['range']['start']['line']}"
82 | 
83 |     def test_file_matching(self):
84 |         """Test that R files are properly matched."""
85 |         from solidlsp.ls_config import Language
86 | 
87 |         matcher = Language.R.get_source_fn_matcher()
88 | 
89 |         assert matcher.is_relevant_filename("script.R")
90 |         assert matcher.is_relevant_filename("analysis.r")
91 |         assert not matcher.is_relevant_filename("script.py")
92 |         assert not matcher.is_relevant_filename("README.md")
93 | 
94 |     def test_r_language_enum(self):
95 |         """Test R language enum value."""
96 |         assert Language.R == "r"
97 |         assert str(Language.R) == "r"
98 | 
```

--------------------------------------------------------------------------------
/test/serena/config/test_serena_config.py:
--------------------------------------------------------------------------------

```python
  1 | import shutil
  2 | import tempfile
  3 | from pathlib import Path
  4 | 
  5 | import pytest
  6 | 
  7 | from serena.config.serena_config import ProjectConfig
  8 | from solidlsp.ls_config import Language
  9 | 
 10 | 
 11 | class TestProjectConfigAutogenerate:
 12 |     """Test class for ProjectConfig autogeneration functionality."""
 13 | 
 14 |     def setup_method(self):
 15 |         """Set up test environment before each test method."""
 16 |         # Create a temporary directory for testing
 17 |         self.test_dir = tempfile.mkdtemp()
 18 |         self.project_path = Path(self.test_dir)
 19 | 
 20 |     def teardown_method(self):
 21 |         """Clean up test environment after each test method."""
 22 |         # Remove the temporary directory
 23 |         shutil.rmtree(self.test_dir)
 24 | 
 25 |     def test_autogenerate_empty_directory(self):
 26 |         """Test that autogenerate raises ValueError with helpful message for empty directory."""
 27 |         with pytest.raises(ValueError) as exc_info:
 28 |             ProjectConfig.autogenerate(self.project_path, save_to_disk=False)
 29 | 
 30 |         error_message = str(exc_info.value)
 31 |         assert "No source files found" in error_message
 32 | 
 33 |     def test_autogenerate_with_python_files(self):
 34 |         """Test successful autogeneration with Python source files."""
 35 |         # Create a Python file
 36 |         python_file = self.project_path / "main.py"
 37 |         python_file.write_text("def hello():\n    print('Hello, world!')\n")
 38 | 
 39 |         # Run autogenerate
 40 |         config = ProjectConfig.autogenerate(self.project_path, save_to_disk=False)
 41 | 
 42 |         # Verify the configuration
 43 |         assert config.project_name == self.project_path.name
 44 |         assert config.languages == [Language.PYTHON]
 45 | 
 46 |     def test_autogenerate_with_js_files(self):
 47 |         """Test successful autogeneration with JavaScript source files."""
 48 |         # Create files for multiple languages
 49 |         (self.project_path / "small.js").write_text("console.log('JS');")
 50 | 
 51 |         # Run autogenerate - should pick Python as dominant
 52 |         config = ProjectConfig.autogenerate(self.project_path, save_to_disk=False)
 53 | 
 54 |         assert config.languages == [Language.TYPESCRIPT]
 55 | 
 56 |     def test_autogenerate_with_multiple_languages(self):
 57 |         """Test autogeneration picks dominant language when multiple are present."""
 58 |         # Create files for multiple languages
 59 |         (self.project_path / "main.py").write_text("print('Python')")
 60 |         (self.project_path / "util.py").write_text("def util(): pass")
 61 |         (self.project_path / "small.js").write_text("console.log('JS');")
 62 | 
 63 |         # Run autogenerate - should pick Python as dominant
 64 |         config = ProjectConfig.autogenerate(self.project_path, save_to_disk=False)
 65 | 
 66 |         assert config.languages == [Language.PYTHON]
 67 | 
 68 |     def test_autogenerate_saves_to_disk(self):
 69 |         """Test that autogenerate can save the configuration to disk."""
 70 |         # Create a Go file
 71 |         go_file = self.project_path / "main.go"
 72 |         go_file.write_text("package main\n\nfunc main() {}\n")
 73 | 
 74 |         # Run autogenerate with save_to_disk=True
 75 |         config = ProjectConfig.autogenerate(self.project_path, save_to_disk=True)
 76 | 
 77 |         # Verify the configuration file was created
 78 |         config_path = self.project_path / ".serena" / "project.yml"
 79 |         assert config_path.exists()
 80 | 
 81 |         # Verify the content
 82 |         assert config.languages == [Language.GO]
 83 | 
 84 |     def test_autogenerate_nonexistent_path(self):
 85 |         """Test that autogenerate raises FileNotFoundError for non-existent path."""
 86 |         non_existent = self.project_path / "does_not_exist"
 87 | 
 88 |         with pytest.raises(FileNotFoundError) as exc_info:
 89 |             ProjectConfig.autogenerate(non_existent, save_to_disk=False)
 90 | 
 91 |         assert "Project root not found" in str(exc_info.value)
 92 | 
 93 |     def test_autogenerate_with_gitignored_files_only(self):
 94 |         """Test autogenerate behavior when only gitignored files exist."""
 95 |         # Create a .gitignore that ignores all Python files
 96 |         gitignore = self.project_path / ".gitignore"
 97 |         gitignore.write_text("*.py\n")
 98 | 
 99 |         # Create Python files that will be ignored
100 |         (self.project_path / "ignored.py").write_text("print('ignored')")
101 | 
102 |         # Should still raise ValueError as no source files are detected
103 |         with pytest.raises(ValueError) as exc_info:
104 |             ProjectConfig.autogenerate(self.project_path, save_to_disk=False)
105 | 
106 |         assert "No source files found" in str(exc_info.value)
107 | 
108 |     def test_autogenerate_custom_project_name(self):
109 |         """Test autogenerate with custom project name."""
110 |         # Create a TypeScript file
111 |         ts_file = self.project_path / "index.ts"
112 |         ts_file.write_text("const greeting: string = 'Hello';\n")
113 | 
114 |         # Run autogenerate with custom name
115 |         custom_name = "my-custom-project"
116 |         config = ProjectConfig.autogenerate(self.project_path, project_name=custom_name, save_to_disk=False)
117 | 
118 |         assert config.project_name == custom_name
119 |         assert config.languages == [Language.TYPESCRIPT]
120 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/groovy/test_groovy_basic.py:
--------------------------------------------------------------------------------

```python
  1 | import os
  2 | from pathlib import Path
  3 | 
  4 | import pytest
  5 | 
  6 | from serena.constants import SERENA_MANAGED_DIR_NAME
  7 | from solidlsp import SolidLanguageServer
  8 | from solidlsp.ls_config import Language, LanguageServerConfig
  9 | from solidlsp.ls_utils import SymbolUtils
 10 | from solidlsp.settings import SolidLSPSettings
 11 | 
 12 | 
 13 | @pytest.mark.groovy
 14 | class TestGroovyLanguageServer:
 15 |     @classmethod
 16 |     def setup_class(cls):
 17 |         """
 18 |         Set up test class with Groovy test repository.
 19 |         """
 20 |         cls.test_repo_path = Path(__file__).parent.parent.parent / "resources" / "repos" / "groovy" / "test_repo"
 21 | 
 22 |         if not cls.test_repo_path.exists():
 23 |             pytest.skip("Groovy test repository not found")
 24 | 
 25 |         # Use JAR path from environment variable
 26 |         ls_jar_path = os.environ.get("GROOVY_LS_JAR_PATH")
 27 |         if not ls_jar_path or not os.path.exists(ls_jar_path):
 28 |             pytest.skip(
 29 |                 "Groovy Language Server JAR not found. Set GROOVY_LS_JAR_PATH environment variable to run tests.",
 30 |                 allow_module_level=True,
 31 |             )
 32 | 
 33 |         # Get JAR options from environment variable
 34 |         ls_jar_options = os.environ.get("GROOVY_LS_JAR_OPTIONS", "")
 35 |         ls_java_home_path = os.environ.get("GROOVY_LS_JAVA_HOME_PATH")
 36 | 
 37 |         groovy_settings = {"ls_jar_path": ls_jar_path, "ls_jar_options": ls_jar_options}
 38 |         if ls_java_home_path:
 39 |             groovy_settings["ls_java_home_path"] = ls_java_home_path
 40 | 
 41 |         # Create language server directly with Groovy-specific settings
 42 |         repo_path = str(cls.test_repo_path)
 43 |         config = LanguageServerConfig(code_language=Language.GROOVY, ignored_paths=[], trace_lsp_communication=False)
 44 | 
 45 |         solidlsp_settings = SolidLSPSettings(
 46 |             solidlsp_dir=str(Path.home() / ".serena"),
 47 |             project_data_relative_path=SERENA_MANAGED_DIR_NAME,
 48 |             ls_specific_settings={Language.GROOVY: groovy_settings},
 49 |         )
 50 | 
 51 |         cls.language_server = SolidLanguageServer.create(config, repo_path, solidlsp_settings=solidlsp_settings)
 52 |         cls.language_server.start()
 53 | 
 54 |     @classmethod
 55 |     def teardown_class(cls):
 56 |         """
 57 |         Clean up language server.
 58 |         """
 59 |         if hasattr(cls, "language_server"):
 60 |             cls.language_server.stop()
 61 | 
 62 |     def test_find_symbol(self) -> None:
 63 |         symbols = self.language_server.request_full_symbol_tree()
 64 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main class not found in symbol tree"
 65 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils class not found in symbol tree"
 66 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model class not found in symbol tree"
 67 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "ModelUser"), "ModelUser class not found in symbol tree"
 68 | 
 69 |     def test_find_referencing_class_symbols(self) -> None:
 70 |         file_path = os.path.join("src", "main", "groovy", "com", "example", "Utils.groovy")
 71 |         refs = self.language_server.request_references(file_path, 3, 6)
 72 |         assert any("Main.groovy" in ref.get("relativePath", "") for ref in refs), "Utils should be referenced from Main.groovy"
 73 | 
 74 |         file_path = os.path.join("src", "main", "groovy", "com", "example", "Model.groovy")
 75 |         symbols = self.language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
 76 |         model_symbol = None
 77 |         for sym in symbols[0]:
 78 |             if sym.get("name") == "com.example.Model" and sym.get("kind") == 5:
 79 |                 model_symbol = sym
 80 |                 break
 81 |         assert model_symbol is not None, "Could not find 'Model' class symbol in Model.groovy"
 82 | 
 83 |         if "selectionRange" in model_symbol:
 84 |             sel_start = model_symbol["selectionRange"]["start"]
 85 |         else:
 86 |             sel_start = model_symbol["range"]["start"]
 87 |         refs = self.language_server.request_references(file_path, sel_start["line"], sel_start["character"])
 88 | 
 89 |         main_refs = [ref for ref in refs if "Main.groovy" in ref.get("relativePath", "")]
 90 |         assert len(main_refs) >= 2, f"Model should be referenced from Main.groovy at least 2 times, found {len(main_refs)}"
 91 | 
 92 |         model_user_refs = [ref for ref in refs if "ModelUser.groovy" in ref.get("relativePath", "")]
 93 |         assert len(model_user_refs) >= 1, f"Model should be referenced from ModelUser.groovy at least 1 time, found {len(model_user_refs)}"
 94 | 
 95 |     def test_overview_methods(self) -> None:
 96 |         symbols = self.language_server.request_full_symbol_tree()
 97 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main missing from overview"
 98 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils missing from overview"
 99 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model missing from overview"
100 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "ModelUser"), "ModelUser missing from overview"
101 | 
```

--------------------------------------------------------------------------------
/test/resources/repos/python/test_repo/scripts/run_app.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python
  2 | """
  3 | Main entry point script for the test_repo application.
  4 | 
  5 | This script demonstrates how a typical application entry point would be structured,
  6 | with command-line arguments, configuration loading, and service initialization.
  7 | """
  8 | 
  9 | import argparse
 10 | import json
 11 | import logging
 12 | import os
 13 | import sys
 14 | from typing import Any
 15 | 
 16 | # Add parent directory to path to make imports work
 17 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
 18 | 
 19 | from test_repo.models import Item, User
 20 | from test_repo.services import ItemService, UserService
 21 | 
 22 | # Configure logging
 23 | logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
 24 | logger = logging.getLogger(__name__)
 25 | 
 26 | 
 27 | def parse_args():
 28 |     """Parse command line arguments."""
 29 |     parser = argparse.ArgumentParser(description="Test Repo Application")
 30 | 
 31 |     parser.add_argument("--config", type=str, default="config.json", help="Path to configuration file")
 32 | 
 33 |     parser.add_argument("--mode", choices=["user", "item", "both"], default="both", help="Operation mode")
 34 | 
 35 |     parser.add_argument("--verbose", action="store_true", help="Enable verbose logging")
 36 | 
 37 |     return parser.parse_args()
 38 | 
 39 | 
 40 | def load_config(config_path: str) -> dict[str, Any]:
 41 |     """Load configuration from a JSON file."""
 42 |     if not os.path.exists(config_path):
 43 |         logger.warning(f"Configuration file not found: {config_path}")
 44 |         return {}
 45 | 
 46 |     try:
 47 |         with open(config_path, encoding="utf-8") as f:
 48 |             return json.load(f)
 49 |     except json.JSONDecodeError:
 50 |         logger.error(f"Invalid JSON in configuration file: {config_path}")
 51 |         return {}
 52 |     except Exception as e:
 53 |         logger.error(f"Error loading configuration: {e}")
 54 |         return {}
 55 | 
 56 | 
 57 | def create_sample_users(service: UserService, count: int = 3) -> list[User]:
 58 |     """Create sample users for demonstration."""
 59 |     users = []
 60 | 
 61 |     # Create admin user
 62 |     admin = service.create_user(name="Admin User", email="[email protected]", roles=["admin"])
 63 |     users.append(admin)
 64 | 
 65 |     # Create regular users
 66 |     for i in range(count - 1):
 67 |         user = service.create_user(name=f"User {i + 1}", email=f"user{i + 1}@example.com", roles=["user"])
 68 |         users.append(user)
 69 | 
 70 |     return users
 71 | 
 72 | 
 73 | def create_sample_items(service: ItemService, count: int = 5) -> list[Item]:
 74 |     """Create sample items for demonstration."""
 75 |     categories = ["Electronics", "Books", "Clothing", "Food", "Other"]
 76 |     items = []
 77 | 
 78 |     for i in range(count):
 79 |         category = categories[i % len(categories)]
 80 |         item = service.create_item(name=f"Item {i + 1}", price=10.0 * (i + 1), category=category)
 81 |         items.append(item)
 82 | 
 83 |     return items
 84 | 
 85 | 
 86 | def run_user_operations(service: UserService, config: dict[str, Any]) -> None:
 87 |     """Run operations related to users."""
 88 |     logger.info("Running user operations")
 89 | 
 90 |     # Get configuration
 91 |     user_count = config.get("user_count", 3)
 92 | 
 93 |     # Create users
 94 |     users = create_sample_users(service, user_count)
 95 |     logger.info(f"Created {len(users)} users")
 96 | 
 97 |     # Demonstrate some operations
 98 |     for user in users:
 99 |         logger.info(f"User: {user.name} (ID: {user.id})")
100 | 
101 |         # Access a method to demonstrate method calls
102 |         if user.has_role("admin"):
103 |             logger.info(f"{user.name} is an admin")
104 | 
105 |     # Lookup a user
106 |     found_user = service.get_user(users[0].id)
107 |     if found_user:
108 |         logger.info(f"Found user: {found_user.name}")
109 | 
110 | 
111 | def run_item_operations(service: ItemService, config: dict[str, Any]) -> None:
112 |     """Run operations related to items."""
113 |     logger.info("Running item operations")
114 | 
115 |     # Get configuration
116 |     item_count = config.get("item_count", 5)
117 | 
118 |     # Create items
119 |     items = create_sample_items(service, item_count)
120 |     logger.info(f"Created {len(items)} items")
121 | 
122 |     # Demonstrate some operations
123 |     total_price = 0.0
124 |     for item in items:
125 |         price_display = item.get_display_price()
126 |         logger.info(f"Item: {item.name}, Price: {price_display}")
127 |         total_price += item.price
128 | 
129 |     logger.info(f"Total price of all items: ${total_price:.2f}")
130 | 
131 | 
132 | def main():
133 |     """Main entry point for the application."""
134 |     # Parse command line arguments
135 |     args = parse_args()
136 | 
137 |     # Configure logging level
138 |     if args.verbose:
139 |         logging.getLogger().setLevel(logging.DEBUG)
140 | 
141 |     logger.info("Starting Test Repo Application")
142 | 
143 |     # Load configuration
144 |     config = load_config(args.config)
145 |     logger.debug(f"Loaded configuration: {config}")
146 | 
147 |     # Initialize services
148 |     user_service = UserService()
149 |     item_service = ItemService()
150 | 
151 |     # Run operations based on mode
152 |     if args.mode in ("user", "both"):
153 |         run_user_operations(user_service, config)
154 | 
155 |     if args.mode in ("item", "both"):
156 |         run_item_operations(item_service, config)
157 | 
158 |     logger.info("Application completed successfully")
159 | 
160 | 
161 | item_reference = Item(id="1", name="Item 1", price=10.0, category="Electronics")
162 | 
163 | if __name__ == "__main__":
164 |     main()
165 | 
```

--------------------------------------------------------------------------------
/test/resources/repos/vue/test_repo/src/App.vue:
--------------------------------------------------------------------------------

```vue
  1 | <script setup lang="ts">
  2 | import { ref, computed, watch, watchEffect, onMounted } from 'vue'
  3 | import { useCalculatorStore } from '@/stores/calculator'
  4 | import { useThemeProvider } from '@/composables/useTheme'
  5 | import { useTimeFormatter } from '@/composables/useFormatter'
  6 | import CalculatorInput from '@/components/CalculatorInput.vue'
  7 | import CalculatorDisplay from '@/components/CalculatorDisplay.vue'
  8 | 
  9 | // Get the calculator store
 10 | const store = useCalculatorStore()
 11 | 
 12 | // Use theme composable with provide/inject - provides theme to all child components
 13 | const themeManager = useThemeProvider()
 14 | 
 15 | // Use time formatter composable
 16 | const timeFormatter = useTimeFormatter()
 17 | 
 18 | // Local ref for app title
 19 | const appTitle = ref('Vue Calculator')
 20 | 
 21 | // Computed property for app version
 22 | const appVersion = computed((): string => {
 23 |   return '1.0.0'
 24 | })
 25 | 
 26 | // Computed property for greeting message
 27 | const greetingMessage = computed((): string => {
 28 |   const hour = new Date().getHours()
 29 |   if (hour < 12) return 'Good morning'
 30 |   if (hour < 18) return 'Good afternoon'
 31 |   return 'Good evening'
 32 | })
 33 | 
 34 | // Get statistics from store
 35 | const totalCalculations = computed((): number => {
 36 |   return store.history.length
 37 | })
 38 | 
 39 | // Check if calculator is active
 40 | const isCalculatorActive = computed((): boolean => {
 41 |   return store.currentValue !== 0 || store.previousValue !== null
 42 | })
 43 | 
 44 | // Get last calculation time
 45 | const lastCalculationTime = computed((): string => {
 46 |   if (store.history.length === 0) return 'No calculations yet'
 47 |   const lastEntry = store.history[store.history.length - 1]
 48 |   return timeFormatter.getRelativeTime(lastEntry.timestamp)
 49 | })
 50 | 
 51 | // Watch for calculation count changes - demonstrates watchEffect
 52 | watchEffect(() => {
 53 |   document.title = `Calculator (${totalCalculations.value} calculations)`
 54 | })
 55 | 
 56 | // Watch for theme changes - demonstrates watch
 57 | watch(
 58 |   () => themeManager.isDarkMode.value,
 59 |   (isDark) => {
 60 |     console.log(`Theme changed to ${isDark ? 'dark' : 'light'} mode`)
 61 |   }
 62 | )
 63 | 
 64 | // Lifecycle hook
 65 | onMounted(() => {
 66 |   console.log('App component mounted')
 67 |   console.log(`Initial calculations: ${totalCalculations.value}`)
 68 | })
 69 | 
 70 | // Toggle theme method
 71 | const toggleTheme = () => {
 72 |   themeManager.toggleDarkMode()
 73 | }
 74 | 
 75 | <template>
 76 |   <div :class="['app', { 'dark-mode': themeManager.isDarkMode }]">
 77 |     <header class="app-header">
 78 |       <h1>{{ appTitle }}</h1>
 79 |       <div class="header-info">
 80 |         <span class="greeting">{{ greetingMessage }}!</span>
 81 |         <span class="version">v{{ appVersion }}</span>
 82 |         <button @click="toggleTheme" class="btn-theme">
 83 |           {{ themeManager.isDarkMode ? '☀️' : '🌙' }}
 84 |         </button>
 85 |       </div>
 86 |     </header>
 87 | 
 88 |     <main class="app-main">
 89 |       <div class="calculator-container">
 90 |         <div class="stats-bar">
 91 |           <span>Total Calculations: {{ totalCalculations }}</span>
 92 |           <span :class="{ 'active-indicator': isCalculatorActive }">
 93 |             {{ isCalculatorActive ? 'Active' : 'Idle' }}
 94 |           </span>
 95 |           <span class="last-calc-time">{{ lastCalculationTime }}</span>
 96 |         </div>
 97 | 
 98 |         <div class="calculator-grid">
 99 |           <CalculatorInput />
100 |           <CalculatorDisplay />
101 |         </div>
102 |       </div>
103 |     </main>
104 | 
105 |     <footer class="app-footer">
106 |       <p>Built with Vue 3 + Pinia + TypeScript</p>
107 |     </footer>
108 |   </div>
109 | </template>
110 | 
111 | <style scoped>
112 | .app {
113 |   min-height: 100vh;
114 |   display: flex;
115 |   flex-direction: column;
116 |   background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
117 |   transition: background 0.3s ease;
118 | }
119 | 
120 | .app.dark-mode {
121 |   background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
122 | }
123 | 
124 | .app-header {
125 |   padding: 2rem;
126 |   color: white;
127 |   text-align: center;
128 | }
129 | 
130 | .app-header h1 {
131 |   margin: 0 0 1rem 0;
132 |   font-size: 2.5rem;
133 | }
134 | 
135 | .header-info {
136 |   display: flex;
137 |   justify-content: center;
138 |   align-items: center;
139 |   gap: 1rem;
140 | }
141 | 
142 | .greeting {
143 |   font-size: 1.1rem;
144 | }
145 | 
146 | .version {
147 |   font-size: 0.9rem;
148 |   opacity: 0.8;
149 | }
150 | 
151 | .btn-theme {
152 |   background: rgba(255, 255, 255, 0.2);
153 |   border: none;
154 |   border-radius: 50%;
155 |   width: 40px;
156 |   height: 40px;
157 |   font-size: 1.2rem;
158 |   cursor: pointer;
159 |   transition: background 0.2s;
160 | }
161 | 
162 | .btn-theme:hover {
163 |   background: rgba(255, 255, 255, 0.3);
164 | }
165 | 
166 | .app-main {
167 |   flex: 1;
168 |   display: flex;
169 |   justify-content: center;
170 |   align-items: center;
171 |   padding: 2rem;
172 | }
173 | 
174 | .calculator-container {
175 |   width: 100%;
176 |   max-width: 1200px;
177 |   display: flex;
178 |   flex-direction: column;
179 |   gap: 1rem;
180 | }
181 | 
182 | .stats-bar {
183 |   display: flex;
184 |   justify-content: space-between;
185 |   align-items: center;
186 |   gap: 1rem;
187 |   padding: 1rem;
188 |   background: rgba(255, 255, 255, 0.9);
189 |   border-radius: 8px;
190 |   font-weight: 500;
191 | }
192 | 
193 | .last-calc-time {
194 |   font-size: 0.9rem;
195 |   color: #666;
196 |   font-style: italic;
197 | }
198 | 
199 | .active-indicator {
200 |   color: #4caf50;
201 |   font-weight: bold;
202 | }
203 | 
204 | .active-indicator:not(.active-indicator) {
205 |   color: #999;
206 | }
207 | 
208 | .calculator-grid {
209 |   display: grid;
210 |   grid-template-columns: 1fr 1fr;
211 |   gap: 2rem;
212 | }
213 | 
214 | @media (max-width: 768px) {
215 |   .calculator-grid {
216 |     grid-template-columns: 1fr;
217 |   }
218 | }
219 | 
220 | .app-footer {
221 |   padding: 1rem;
222 |   text-align: center;
223 |   color: white;
224 |   opacity: 0.8;
225 | }
226 | 
227 | .app-footer p {
228 |   margin: 0;
229 | }
230 | </style>
231 | 
```

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

```python
  1 | """
  2 | Tools supporting the general workflow of the agent
  3 | """
  4 | 
  5 | import json
  6 | import platform
  7 | 
  8 | from serena.tools import Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional
  9 | 
 10 | 
 11 | class CheckOnboardingPerformedTool(Tool):
 12 |     """
 13 |     Checks whether project onboarding was already performed.
 14 |     """
 15 | 
 16 |     def apply(self) -> str:
 17 |         """
 18 |         Checks whether project onboarding was already performed.
 19 |         You should always call this tool before beginning to actually work on the project/after activating a project.
 20 |         """
 21 |         from .memory_tools import ListMemoriesTool
 22 | 
 23 |         list_memories_tool = self.agent.get_tool(ListMemoriesTool)
 24 |         memories = json.loads(list_memories_tool.apply())
 25 |         if len(memories) == 0:
 26 |             return (
 27 |                 "Onboarding not performed yet (no memories available). "
 28 |                 + "You should perform onboarding by calling the `onboarding` tool before proceeding with the task."
 29 |             )
 30 |         else:
 31 |             return f"""The onboarding was already performed, below is the list of available memories.
 32 |             Do not read them immediately, just remember that they exist and that you can read them later, if it is necessary
 33 |             for the current task.
 34 |             Some memories may be based on previous conversations, others may be general for the current project.
 35 |             You should be able to tell which one you need based on the name of the memory.
 36 |             
 37 |             {memories}"""
 38 | 
 39 | 
 40 | class OnboardingTool(Tool):
 41 |     """
 42 |     Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
 43 |     """
 44 | 
 45 |     def apply(self) -> str:
 46 |         """
 47 |         Call this tool if onboarding was not performed yet.
 48 |         You will call this tool at most once per conversation.
 49 | 
 50 |         :return: instructions on how to create the onboarding information
 51 |         """
 52 |         system = platform.system()
 53 |         return self.prompt_factory.create_onboarding_prompt(system=system)
 54 | 
 55 | 
 56 | class ThinkAboutCollectedInformationTool(Tool):
 57 |     """
 58 |     Thinking tool for pondering the completeness of collected information.
 59 |     """
 60 | 
 61 |     def apply(self) -> str:
 62 |         """
 63 |         Think about the collected information and whether it is sufficient and relevant.
 64 |         This tool should ALWAYS be called after you have completed a non-trivial sequence of searching steps like
 65 |         find_symbol, find_referencing_symbols, search_files_for_pattern, read_file, etc.
 66 |         """
 67 |         return self.prompt_factory.create_think_about_collected_information()
 68 | 
 69 | 
 70 | class ThinkAboutTaskAdherenceTool(Tool):
 71 |     """
 72 |     Thinking tool for determining whether the agent is still on track with the current task.
 73 |     """
 74 | 
 75 |     def apply(self) -> str:
 76 |         """
 77 |         Think about the task at hand and whether you are still on track.
 78 |         Especially important if the conversation has been going on for a while and there
 79 |         has been a lot of back and forth.
 80 | 
 81 |         This tool should ALWAYS be called before you insert, replace, or delete code.
 82 |         """
 83 |         return self.prompt_factory.create_think_about_task_adherence()
 84 | 
 85 | 
 86 | class ThinkAboutWhetherYouAreDoneTool(Tool):
 87 |     """
 88 |     Thinking tool for determining whether the task is truly completed.
 89 |     """
 90 | 
 91 |     def apply(self) -> str:
 92 |         """
 93 |         Whenever you feel that you are done with what the user has asked for, it is important to call this tool.
 94 |         """
 95 |         return self.prompt_factory.create_think_about_whether_you_are_done()
 96 | 
 97 | 
 98 | class SummarizeChangesTool(Tool, ToolMarkerOptional):
 99 |     """
100 |     Provides instructions for summarizing the changes made to the codebase.
101 |     """
102 | 
103 |     def apply(self) -> str:
104 |         """
105 |         Summarize the changes you have made to the codebase.
106 |         This tool should always be called after you have fully completed any non-trivial coding task,
107 |         but only after the think_about_whether_you_are_done call.
108 |         """
109 |         return self.prompt_factory.create_summarize_changes()
110 | 
111 | 
112 | class PrepareForNewConversationTool(Tool):
113 |     """
114 |     Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
115 |     """
116 | 
117 |     def apply(self) -> str:
118 |         """
119 |         Instructions for preparing for a new conversation. This tool should only be called on explicit user request.
120 |         """
121 |         return self.prompt_factory.create_prepare_for_new_conversation()
122 | 
123 | 
124 | class InitialInstructionsTool(Tool, ToolMarkerDoesNotRequireActiveProject):
125 |     """
126 |     Provides instructions on how to use the Serena toolbox.
127 |     Should only be used in settings where the system prompt is not read automatically by the client.
128 | 
129 |     NOTE: Some MCP clients (including Claude Desktop) do not read the system prompt automatically!
130 |     """
131 | 
132 |     def apply(self) -> str:
133 |         """
134 |         Provides the 'Serena Instructions Manual', which contains essential information on how to use the Serena toolbox.
135 |         IMPORTANT: If you have not yet read the manual, call this tool immediately after you are given your task by the user,
136 |         as it will critically inform you!
137 |         """
138 |         return self.agent.create_system_prompt()
139 | 
```

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

```python
  1 | """
  2 | Basic integration tests for the Elixir language server functionality.
  3 | 
  4 | These tests validate the functionality of the language server APIs
  5 | like request_references using the test repository.
  6 | """
  7 | 
  8 | import os
  9 | 
 10 | import pytest
 11 | 
 12 | from solidlsp import SolidLanguageServer
 13 | from solidlsp.ls_config import Language
 14 | 
 15 | from . import EXPERT_UNAVAILABLE, EXPERT_UNAVAILABLE_REASON
 16 | 
 17 | # These marks will be applied to all tests in this module
 18 | pytestmark = [pytest.mark.elixir, pytest.mark.skipif(EXPERT_UNAVAILABLE, reason=f"Next LS not available: {EXPERT_UNAVAILABLE_REASON}")]
 19 | 
 20 | 
 21 | class TestElixirBasic:
 22 |     """Basic Elixir language server functionality tests."""
 23 | 
 24 |     @pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
 25 |     def test_request_references_function_definition(self, language_server: SolidLanguageServer):
 26 |         """Test finding references to a function definition."""
 27 |         file_path = os.path.join("lib", "models.ex")
 28 |         symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
 29 | 
 30 |         # Find the User module's 'new' function
 31 |         user_new_symbol = None
 32 |         for symbol in symbols[0]:  # Top level symbols
 33 |             if symbol.get("name") == "User" and symbol.get("kind") == 2:  # Module
 34 |                 for child in symbol.get("children", []):
 35 |                     if child.get("name", "").startswith("def new(") and child.get("kind") == 12:  # Function
 36 |                         user_new_symbol = child
 37 |                         break
 38 |                 break
 39 | 
 40 |         if not user_new_symbol or "selectionRange" not in user_new_symbol:
 41 |             pytest.skip("User.new function or its selectionRange not found")
 42 | 
 43 |         sel_start = user_new_symbol["selectionRange"]["start"]
 44 |         references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
 45 | 
 46 |         assert references is not None
 47 |         assert len(references) > 0
 48 | 
 49 |         # Should find at least one reference (the definition itself)
 50 |         found_definition = any(ref["uri"].endswith("models.ex") for ref in references)
 51 |         assert found_definition, "Should find the function definition"
 52 | 
 53 |     @pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
 54 |     def test_request_references_create_user_function(self, language_server: SolidLanguageServer):
 55 |         """Test finding references to create_user function."""
 56 |         file_path = os.path.join("lib", "services.ex")
 57 |         symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
 58 | 
 59 |         # Find the UserService module's 'create_user' function
 60 |         create_user_symbol = None
 61 |         for symbol in symbols[0]:  # Top level symbols
 62 |             if symbol.get("name") == "UserService" and symbol.get("kind") == 2:  # Module
 63 |                 for child in symbol.get("children", []):
 64 |                     if child.get("name", "").startswith("def create_user(") and child.get("kind") == 12:  # Function
 65 |                         create_user_symbol = child
 66 |                         break
 67 |                 break
 68 | 
 69 |         if not create_user_symbol or "selectionRange" not in create_user_symbol:
 70 |             pytest.skip("UserService.create_user function or its selectionRange not found")
 71 | 
 72 |         sel_start = create_user_symbol["selectionRange"]["start"]
 73 |         references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
 74 | 
 75 |         assert references is not None
 76 |         assert len(references) > 0
 77 | 
 78 |     @pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
 79 |     def test_request_referencing_symbols_function(self, language_server: SolidLanguageServer):
 80 |         """Test finding symbols that reference a specific function."""
 81 |         file_path = os.path.join("lib", "models.ex")
 82 |         symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
 83 | 
 84 |         # Find the User module's 'new' function
 85 |         user_new_symbol = None
 86 |         for symbol in symbols[0]:  # Top level symbols
 87 |             if symbol.get("name") == "User" and symbol.get("kind") == 2:  # Module
 88 |                 for child in symbol.get("children", []):
 89 |                     if child.get("name", "").startswith("def new(") and child.get("kind") == 12:  # Function
 90 |                         user_new_symbol = child
 91 |                         break
 92 |                 break
 93 | 
 94 |         if not user_new_symbol or "selectionRange" not in user_new_symbol:
 95 |             pytest.skip("User.new function or its selectionRange not found")
 96 | 
 97 |         sel_start = user_new_symbol["selectionRange"]["start"]
 98 |         referencing_symbols = language_server.request_referencing_symbols(file_path, sel_start["line"], sel_start["character"])
 99 | 
100 |         assert referencing_symbols is not None
101 | 
102 |     @pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
103 |     def test_timeout_enumeration_bug(self, language_server: SolidLanguageServer):
104 |         """Test that enumeration doesn't timeout (regression test)."""
105 |         # This should complete without timing out
106 |         symbols = language_server.request_document_symbols("lib/models.ex").get_all_symbols_and_roots()
107 |         assert symbols is not None
108 | 
109 |         # Test multiple symbol requests in succession
110 |         for _ in range(3):
111 |             symbols = language_server.request_document_symbols("lib/services.ex").get_all_symbols_and_roots()
112 |             assert symbols is not None
113 | 
```

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

```python
  1 | import os
  2 | from unittest.mock import MagicMock, Mock, patch
  3 | 
  4 | import pytest
  5 | 
  6 | from serena.util.exception import is_headless_environment, show_fatal_exception_safe
  7 | 
  8 | 
  9 | class TestHeadlessEnvironmentDetection:
 10 |     """Test class for headless environment detection functionality."""
 11 | 
 12 |     def test_is_headless_no_display(self):
 13 |         """Test that environment without DISPLAY is detected as headless on Linux."""
 14 |         with patch("sys.platform", "linux"):
 15 |             with patch.dict(os.environ, {}, clear=True):
 16 |                 assert is_headless_environment() is True
 17 | 
 18 |     def test_is_headless_ssh_connection(self):
 19 |         """Test that SSH sessions are detected as headless."""
 20 |         with patch("sys.platform", "linux"):
 21 |             with patch.dict(os.environ, {"SSH_CONNECTION": "192.168.1.1 22 192.168.1.2 22", "DISPLAY": ":0"}):
 22 |                 assert is_headless_environment() is True
 23 | 
 24 |             with patch.dict(os.environ, {"SSH_CLIENT": "192.168.1.1 22 22", "DISPLAY": ":0"}):
 25 |                 assert is_headless_environment() is True
 26 | 
 27 |     def test_is_headless_wsl(self):
 28 |         """Test that WSL environment is detected as headless."""
 29 |         # Skip this test on Windows since os.uname doesn't exist
 30 |         if not hasattr(os, "uname"):
 31 |             pytest.skip("os.uname not available on this platform")
 32 | 
 33 |         with patch("sys.platform", "linux"):
 34 |             with patch("os.uname") as mock_uname:
 35 |                 mock_uname.return_value = Mock(release="5.15.153.1-microsoft-standard-WSL2")
 36 |                 with patch.dict(os.environ, {"DISPLAY": ":0"}):
 37 |                     assert is_headless_environment() is True
 38 | 
 39 |     def test_is_headless_docker(self):
 40 |         """Test that Docker containers are detected as headless."""
 41 |         with patch("sys.platform", "linux"):
 42 |             # Test with CI environment variable
 43 |             with patch.dict(os.environ, {"CI": "true", "DISPLAY": ":0"}):
 44 |                 assert is_headless_environment() is True
 45 | 
 46 |             # Test with CONTAINER environment variable
 47 |             with patch.dict(os.environ, {"CONTAINER": "docker", "DISPLAY": ":0"}):
 48 |                 assert is_headless_environment() is True
 49 | 
 50 |             # Test with .dockerenv file
 51 |             with patch("os.path.exists") as mock_exists:
 52 |                 mock_exists.return_value = True
 53 |                 with patch.dict(os.environ, {"DISPLAY": ":0"}):
 54 |                     assert is_headless_environment() is True
 55 | 
 56 |     def test_is_not_headless_windows(self):
 57 |         """Test that Windows is never detected as headless."""
 58 |         with patch("sys.platform", "win32"):
 59 |             # Even without DISPLAY, Windows should not be headless
 60 |             with patch.dict(os.environ, {}, clear=True):
 61 |                 assert is_headless_environment() is False
 62 | 
 63 | 
 64 | class TestShowFatalExceptionSafe:
 65 |     """Test class for safe fatal exception display functionality."""
 66 | 
 67 |     @patch("serena.util.exception.is_headless_environment", return_value=True)
 68 |     @patch("serena.util.exception.log")
 69 |     def test_show_fatal_exception_safe_headless(self, mock_log, mock_is_headless):
 70 |         """Test that GUI is not attempted in headless environment."""
 71 |         test_exception = ValueError("Test error")
 72 | 
 73 |         # The import should never happen in headless mode
 74 |         with patch("serena.gui_log_viewer.show_fatal_exception") as mock_show_gui:
 75 |             show_fatal_exception_safe(test_exception)
 76 |             mock_show_gui.assert_not_called()
 77 | 
 78 |         # Verify debug log about skipping GUI
 79 |         mock_log.debug.assert_called_once_with("Skipping GUI error display in headless environment")
 80 | 
 81 |     @patch("serena.util.exception.is_headless_environment", return_value=False)
 82 |     @patch("serena.util.exception.log")
 83 |     def test_show_fatal_exception_safe_with_gui(self, mock_log, mock_is_headless):
 84 |         """Test that GUI is attempted when not in headless environment."""
 85 |         test_exception = ValueError("Test error")
 86 | 
 87 |         # Mock the GUI function
 88 |         with patch("serena.gui_log_viewer.show_fatal_exception") as mock_show_gui:
 89 |             show_fatal_exception_safe(test_exception)
 90 |             mock_show_gui.assert_called_once_with(test_exception)
 91 | 
 92 |     @patch("serena.util.exception.is_headless_environment", return_value=False)
 93 |     @patch("serena.util.exception.log")
 94 |     def test_show_fatal_exception_safe_gui_failure(self, mock_log, mock_is_headless):
 95 |         """Test graceful handling when GUI display fails."""
 96 |         test_exception = ValueError("Test error")
 97 |         gui_error = ImportError("No module named 'tkinter'")
 98 | 
 99 |         # Mock the GUI function to raise an exception
100 |         with patch("serena.gui_log_viewer.show_fatal_exception", side_effect=gui_error):
101 |             show_fatal_exception_safe(test_exception)
102 | 
103 |         # Verify debug log about GUI failure
104 |         mock_log.debug.assert_called_with(f"Failed to show GUI error dialog: {gui_error}")
105 | 
106 |     def test_show_fatal_exception_safe_prints_to_stderr(self):
107 |         """Test that exceptions are always printed to stderr."""
108 |         test_exception = ValueError("Test error message")
109 | 
110 |         with patch("sys.stderr", new_callable=MagicMock) as mock_stderr:
111 |             with patch("serena.util.exception.is_headless_environment", return_value=True):
112 |                 with patch("serena.util.exception.log"):
113 |                     show_fatal_exception_safe(test_exception)
114 | 
115 |         # Verify print was called with the correct arguments
116 |         mock_stderr.write.assert_any_call("Fatal exception: Test error message")
117 | 
```

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

```yaml
 1 | # Some of Serena's tools are just outputting a fixed text block without doing anything else.
 2 | # Such tools are meant to encourage the agent to think in a certain way, to stay on track
 3 | # and so on. The (templates for) outputs of these tools are contained here.
 4 | prompts:
 5 |   onboarding_prompt: |
 6 |     You are viewing the project for the first time.
 7 |     Your task is to assemble relevant high-level information about the project which
 8 |     will be saved to memory files in the following steps.
 9 |     The information should be sufficient to understand what the project is about,
10 |     and the most important commands for developing code.
11 |     The project is being developed on the system: {{ system }}.
12 | 
13 |     You need to identify at least the following information:
14 |     * the project's purpose
15 |     * the tech stack used
16 |     * the code style and conventions used (including naming, type hints, docstrings, etc.)
17 |     * which commands to run when a task is completed (linting, formatting, testing, etc.)
18 |     * the rough structure of the codebase
19 |     * the commands for testing, formatting, and linting
20 |     * the commands for running the entrypoints of the project
21 |     * the util commands for the system, like `git`, `ls`, `cd`, `grep`, `find`, etc. Keep in mind that the system is {{ system }},
22 |       so the commands might be different than on a regular unix system.
23 |     * whether there are particular guidelines, styles, design patterns, etc. that one should know about
24 | 
25 |     This list is not exhaustive, you can add more information if you think it is relevant.
26 | 
27 |     For doing that, you will need to acquire information about the project with the corresponding tools.
28 |     Read only the necessary files and directories to avoid loading too much data into memory.
29 |     If you cannot find everything you need from the project itself, you should ask the user for more information.
30 | 
31 |     After collecting all the information, you will use the `write_memory` tool (in multiple calls) to save it to various memory files.
32 |     A particularly important memory file will be the `suggested_commands.md` file, which should contain
33 |     a list of commands that the user should know about to develop code in this project.
34 |     Moreover, you should create memory files for the style and conventions and a dedicated memory file for
35 |     what should be done when a task is completed.
36 |     **Important**: after done with the onboarding task, remember to call the `write_memory` to save the collected information!
37 | 
38 |   think_about_collected_information: |
39 |     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,
40 |     in particular the tools related to symbol discovery? Or do you need to ask the user for more information?
41 |     Think about it step by step and give a summary of the missing information and how it could be acquired.
42 | 
43 |   think_about_task_adherence: |
44 |     Are you deviating from the task at hand? Do you need any additional information to proceed?
45 |     Have you loaded all relevant memory files to see whether your implementation is fully aligned with the
46 |     code style, conventions, and guidelines of the project? If not, adjust your implementation accordingly
47 |     before modifying any code into the codebase.
48 |     Note that it is better to stop and ask the user for clarification
49 |     than to perform large changes which might not be aligned with the user's intentions.
50 |     If you feel like the conversation is deviating too much from the original task, apologize and suggest to the user
51 |     how to proceed. If the conversation became too long, create a summary of the current progress and suggest to the user
52 |     to start a new conversation based on that summary.
53 | 
54 |   think_about_whether_you_are_done: |
55 |     Have you already performed all the steps required by the task? Is it appropriate to run tests and linting, and if so,
56 |     have you done that already? Is it appropriate to adjust non-code files like documentation and config and have you done that already?
57 |     Should new tests be written to cover the changes?
58 |     Note that a task that is just about exploring the codebase does not require running tests or linting.
59 |     Read the corresponding memory files to see what should be done when a task is completed. 
60 | 
61 |   summarize_changes: |
62 |     Summarize all the changes you have made to the codebase over the course of the conversation.
63 |     Explore the diff if needed (e.g. by using `git diff`) to ensure that you have not missed anything.
64 |     Explain whether and how the changes are covered by tests. Explain how to best use the new code, how to understand it,
65 |     which existing code it affects and interacts with. Are there any dangers (like potential breaking changes or potential new problems) 
66 |     that the user should be aware of? Should any new documentation be written or existing documentation updated?
67 |     You can use tools to explore the codebase prior to writing the summary, but don't write any new code in this step until
68 |     the summary is complete.
69 | 
70 |   prepare_for_new_conversation: |
71 |     You have not yet completed the current task but we are running out of context.
72 |     {mode_prepare_for_new_conversation}
73 |     Imagine that you are handing over the task to another person who has access to the
74 |     same tools and memory files as you do, but has not been part of the conversation so far.
75 |     Write a summary that can be used in the next conversation to a memory file using the `write_memory` tool.
76 | 
```

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

```python
  1 | """
  2 | This file provides the implementation of the JSON-RPC client, that launches and
  3 | communicates with the language server.
  4 | 
  5 | The initial implementation of this file was obtained from
  6 | https://github.com/predragnikolic/OLSP under the MIT License with the following terms:
  7 | 
  8 | MIT License
  9 | 
 10 | Copyright (c) 2023 Предраг Николић
 11 | 
 12 | Permission is hereby granted, free of charge, to any person obtaining a copy
 13 | of this software and associated documentation files (the "Software"), to deal
 14 | in the Software without restriction, including without limitation the rights
 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 16 | copies of the Software, and to permit persons to whom the Software is
 17 | furnished to do so, subject to the following conditions:
 18 | 
 19 | The above copyright notice and this permission notice shall be included in all
 20 | copies or substantial portions of the Software.
 21 | 
 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 28 | SOFTWARE.
 29 | """
 30 | 
 31 | import dataclasses
 32 | import json
 33 | import logging
 34 | import os
 35 | from typing import Any, Union
 36 | 
 37 | from .lsp_types import ErrorCodes
 38 | 
 39 | StringDict = dict[str, Any]
 40 | PayloadLike = Union[list[StringDict], StringDict, None, bool]
 41 | CONTENT_LENGTH = "Content-Length: "
 42 | ENCODING = "utf-8"
 43 | log = logging.getLogger(__name__)
 44 | 
 45 | 
 46 | @dataclasses.dataclass
 47 | class ProcessLaunchInfo:
 48 |     """
 49 |     This class is used to store the information required to launch a (language server) process.
 50 |     """
 51 | 
 52 |     cmd: str | list[str]
 53 |     """
 54 |     the command used to launch the process.
 55 |     Specification as a list is preferred (as it is more robust and avoids incorrect quoting of arguments);
 56 |     the string variant is supported for backward compatibility only
 57 |     """
 58 | 
 59 |     env: dict[str, str] = dataclasses.field(default_factory=dict)
 60 |     """
 61 |     the environment variables to set for the process
 62 |     """
 63 | 
 64 |     cwd: str = os.getcwd()
 65 |     """
 66 |     the working directory for the process
 67 |     """
 68 | 
 69 | 
 70 | class LSPError(Exception):
 71 |     def __init__(self, code: ErrorCodes, message: str) -> None:
 72 |         super().__init__(message)
 73 |         self.code = code
 74 | 
 75 |     def to_lsp(self) -> StringDict:
 76 |         return {"code": self.code, "message": super().__str__()}
 77 | 
 78 |     @classmethod
 79 |     def from_lsp(cls, d: StringDict) -> "LSPError":
 80 |         return LSPError(d["code"], d["message"])
 81 | 
 82 |     def __str__(self) -> str:
 83 |         return f"{super().__str__()} ({self.code})"
 84 | 
 85 | 
 86 | def make_response(request_id: Any, params: PayloadLike) -> StringDict:
 87 |     return {"jsonrpc": "2.0", "id": request_id, "result": params}
 88 | 
 89 | 
 90 | def make_error_response(request_id: Any, err: LSPError) -> StringDict:
 91 |     return {"jsonrpc": "2.0", "id": request_id, "error": err.to_lsp()}
 92 | 
 93 | 
 94 | # LSP methods that expect NO params field at all (not even empty object).
 95 | # These methods use Void/unit type in their protocol definition.
 96 | # - shutdown: HLS uses Haskell's Void type, rust-analyzer expects unit
 97 | # - exit: Similar - notification with no params
 98 | # Sending params:{} to these methods causes parse errors like "Cannot parse Void"
 99 | # See: https://www.jsonrpc.org/specification ("params MAY be omitted")
100 | _NO_PARAMS_METHODS = frozenset({"shutdown", "exit"})
101 | 
102 | 
103 | def _build_params_field(method: str, params: PayloadLike) -> StringDict:
104 |     """Build the params portion of a JSON-RPC message based on LSP method requirements.
105 | 
106 |     LSP methods with Void/unit type (shutdown, exit) must omit params field entirely
107 |     to satisfy HLS and rust-analyzer. Other methods send empty {} for None params
108 |     to maintain Delphi/FPC LSP compatibility (PR #851).
109 | 
110 |     Returns a dict that can be merged into the message using ** unpacking.
111 |     """
112 |     if method in _NO_PARAMS_METHODS:
113 |         return {}  # Omit params entirely for Void-type methods
114 |     elif params is not None:
115 |         return {"params": params}
116 |     else:
117 |         return {"params": {}}  # Keep {} for Delphi/FPC compatibility
118 | 
119 | 
120 | def make_notification(method: str, params: PayloadLike) -> StringDict:
121 |     """Create a JSON-RPC 2.0 notification message."""
122 |     return {"jsonrpc": "2.0", "method": method, **_build_params_field(method, params)}
123 | 
124 | 
125 | def make_request(method: str, request_id: Any, params: PayloadLike) -> StringDict:
126 |     """Create a JSON-RPC 2.0 request message."""
127 |     return {"jsonrpc": "2.0", "method": method, "id": request_id, **_build_params_field(method, params)}
128 | 
129 | 
130 | class StopLoopException(Exception):
131 |     pass
132 | 
133 | 
134 | def create_message(payload: PayloadLike) -> tuple[bytes, bytes, bytes]:
135 |     body = json.dumps(payload, check_circular=False, ensure_ascii=False, separators=(",", ":")).encode(ENCODING)
136 |     return (
137 |         f"Content-Length: {len(body)}\r\n".encode(ENCODING),
138 |         "Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n".encode(ENCODING),
139 |         body,
140 |     )
141 | 
142 | 
143 | class MessageType:
144 |     error = 1
145 |     warning = 2
146 |     info = 3
147 |     log = 4
148 | 
149 | 
150 | def content_length(line: bytes) -> int | None:
151 |     if line.startswith(b"Content-Length: "):
152 |         _, value = line.split(b"Content-Length: ")
153 |         value = value.strip()
154 |         try:
155 |             return int(value)
156 |         except ValueError:
157 |             raise ValueError(f"Invalid Content-Length header: {value!r}")
158 |     return None
159 | 
```

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

```python
  1 | """Regal Language Server implementation for Rego policy files."""
  2 | 
  3 | import logging
  4 | import os
  5 | import shutil
  6 | import threading
  7 | 
  8 | from overrides import override
  9 | 
 10 | from solidlsp.ls import SolidLanguageServer
 11 | from solidlsp.ls_config import LanguageServerConfig
 12 | from solidlsp.ls_utils import PathUtils
 13 | from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
 14 | from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
 15 | from solidlsp.settings import SolidLSPSettings
 16 | 
 17 | log = logging.getLogger(__name__)
 18 | 
 19 | 
 20 | class RegalLanguageServer(SolidLanguageServer):
 21 |     """
 22 |     Provides Rego specific instantiation of the LanguageServer class using Regal.
 23 | 
 24 |     Regal is the official linter and language server for Rego (Open Policy Agent's policy language).
 25 |     See: https://github.com/StyraInc/regal
 26 |     """
 27 | 
 28 |     @override
 29 |     def is_ignored_dirname(self, dirname: str) -> bool:
 30 |         return super().is_ignored_dirname(dirname) or dirname in [".regal", ".opa"]
 31 | 
 32 |     def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
 33 |         """
 34 |         Creates a RegalLanguageServer instance.
 35 | 
 36 |         This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
 37 | 
 38 |         :param config: Language server configuration
 39 |         :param repository_root_path: Path to the repository root
 40 |         :param solidlsp_settings: Settings for solidlsp
 41 |         """
 42 |         # Regal should be installed system-wide (via CI or user installation)
 43 |         regal_executable_path = shutil.which("regal")
 44 |         if not regal_executable_path:
 45 |             raise RuntimeError(
 46 |                 "Regal language server not found. Please install it from https://github.com/StyraInc/regal or via your package manager."
 47 |             )
 48 | 
 49 |         super().__init__(
 50 |             config,
 51 |             repository_root_path,
 52 |             ProcessLaunchInfo(cmd=f"{regal_executable_path} language-server", cwd=repository_root_path),
 53 |             "rego",
 54 |             solidlsp_settings,
 55 |         )
 56 |         self.server_ready = threading.Event()
 57 | 
 58 |     @staticmethod
 59 |     def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
 60 |         """
 61 |         Returns the initialize params for the Regal Language Server.
 62 | 
 63 |         :param repository_absolute_path: Absolute path to the repository
 64 |         :return: LSP initialization parameters
 65 |         """
 66 |         root_uri = PathUtils.path_to_uri(repository_absolute_path)
 67 |         return {
 68 |             "processId": os.getpid(),
 69 |             "locale": "en",
 70 |             "rootPath": repository_absolute_path,
 71 |             "rootUri": root_uri,
 72 |             "capabilities": {
 73 |                 "textDocument": {
 74 |                     "synchronization": {"didSave": True, "dynamicRegistration": True},
 75 |                     "completion": {"dynamicRegistration": True, "completionItem": {"snippetSupport": True}},
 76 |                     "definition": {"dynamicRegistration": True},
 77 |                     "references": {"dynamicRegistration": True},
 78 |                     "documentSymbol": {
 79 |                         "dynamicRegistration": True,
 80 |                         "hierarchicalDocumentSymbolSupport": True,
 81 |                         "symbolKind": {"valueSet": list(range(1, 27))},  # type: ignore[arg-type]
 82 |                     },
 83 |                     "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},  # type: ignore[list-item]
 84 |                     "codeAction": {"dynamicRegistration": True},
 85 |                     "formatting": {"dynamicRegistration": True},
 86 |                 },
 87 |                 "workspace": {
 88 |                     "workspaceFolders": True,
 89 |                     "didChangeConfiguration": {"dynamicRegistration": True},
 90 |                     "symbol": {"dynamicRegistration": True},
 91 |                 },
 92 |             },
 93 |             "workspaceFolders": [
 94 |                 {
 95 |                     "name": os.path.basename(repository_absolute_path),
 96 |                     "uri": root_uri,
 97 |                 }
 98 |             ],
 99 |         }
100 | 
101 |     def _start_server(self) -> None:
102 |         """Start Regal language server process and wait for initialization."""
103 | 
104 |         def register_capability_handler(params) -> None:  # type: ignore[no-untyped-def]
105 |             return
106 | 
107 |         def window_log_message(msg) -> None:  # type: ignore[no-untyped-def]
108 |             log.info(f"LSP: window/logMessage: {msg}")
109 | 
110 |         def do_nothing(params) -> None:  # type: ignore[no-untyped-def]
111 |             return
112 | 
113 |         self.server.on_request("client/registerCapability", register_capability_handler)
114 |         self.server.on_notification("window/logMessage", window_log_message)
115 |         self.server.on_notification("$/progress", do_nothing)
116 |         self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
117 | 
118 |         log.info("Starting Regal language server process")
119 |         self.server.start()
120 |         initialize_params = self._get_initialize_params(self.repository_root_path)
121 | 
122 |         log.info(
123 |             "Sending initialize request from LSP client to LSP server and awaiting response",
124 |         )
125 |         init_response = self.server.send.initialize(initialize_params)
126 | 
127 |         # Verify server capabilities
128 |         assert "capabilities" in init_response
129 |         assert "textDocumentSync" in init_response["capabilities"]
130 | 
131 |         self.server.notify.initialized({})
132 |         self.completions_available.set()
133 | 
134 |         # Regal server is ready immediately after initialization
135 |         self.server_ready.set()
136 |         self.server_ready.wait()
137 | 
```

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

```python
  1 | import logging
  2 | import os
  3 | import shutil
  4 | from pathlib import Path
  5 | from typing import Optional, List
  6 | 
  7 | log = logging.getLogger(os.path.basename(__file__))
  8 | 
  9 | TOP_LEVEL_PACKAGE = "serena"
 10 | PROJECT_NAME = "Serena"
 11 | 
 12 | def module_template(module_qualname: str):
 13 |     module_name = module_qualname.split(".")[-1]
 14 |     title = module_name.replace("_", r"\_")
 15 |     return f"""{title}
 16 | {"=" * len(title)}
 17 | 
 18 | .. automodule:: {module_qualname}
 19 |    :members:
 20 |    :undoc-members:
 21 | """
 22 | 
 23 | 
 24 | def index_template(package_name: str, doc_references: Optional[List[str]] = None, text_prefix=""):
 25 |     doc_references = doc_references or ""
 26 |     if doc_references:
 27 |         doc_references = "\n" + "\n".join(f"* :doc:`{ref}`" for ref in doc_references) + "\n"
 28 | 
 29 |     dirname = package_name.split(".")[-1]
 30 |     title = dirname.replace("_", r"\_")
 31 |     if title == TOP_LEVEL_PACKAGE:
 32 |         title = "API Reference"
 33 |     return f"{title}\n{'=' * len(title)}" + text_prefix + doc_references
 34 | 
 35 | 
 36 | def write_to_file(content: str, path: str):
 37 |     os.makedirs(os.path.dirname(path), exist_ok=True)
 38 |     with open(path, "w") as f:
 39 |         f.write(content)
 40 |     os.chmod(path, 0o666)
 41 | 
 42 | 
 43 | _SUBTITLE = (
 44 |     f"\n Here is the autogenerated documentation of the {PROJECT_NAME} API. \n \n "
 45 |     "The Table of Contents to the left has the same structure as the "
 46 |     "repository's package code. The links at each page point to the submodules and subpackages. \n"
 47 | )
 48 | 
 49 | 
 50 | def make_rst(src_root, rst_root, clean=False, overwrite=False, package_prefix=""):
 51 |     """Creates/updates documentation in form of rst files for modules and packages.
 52 | 
 53 |     Does not delete any existing rst files. Thus, rst files for packages or modules that have been removed or renamed
 54 |     should be deleted by hand.
 55 | 
 56 |     This method should be executed from the project's top-level directory
 57 | 
 58 |     :param src_root: path to library base directory, typically "src/<library_name>"
 59 |     :param rst_root: path to the root directory to which .rst files will be written
 60 |     :param clean: whether to completely clean the target directory beforehand, removing any existing .rst files
 61 |     :param overwrite: whether to overwrite existing rst files. This should be used with caution as it will delete
 62 |         all manual changes to documentation files
 63 |     :package_prefix: a prefix to prepend to each module (for the case where the src_root is not the base package),
 64 |         which, if not empty, should end with a "."
 65 |     :return:
 66 |     """
 67 |     rst_root = os.path.abspath(rst_root)
 68 | 
 69 |     if clean and os.path.isdir(rst_root):
 70 |         shutil.rmtree(rst_root)
 71 | 
 72 |     base_package_name = package_prefix + os.path.basename(src_root)
 73 | 
 74 |     # TODO: reduce duplication with same logic for subpackages below
 75 |     files_in_dir = os.listdir(src_root)
 76 |     module_names = [f[:-3] for f in files_in_dir if f.endswith(".py") and not f.startswith("_")]
 77 |     subdir_refs = [
 78 |         f"{f}/index"
 79 |         for f in files_in_dir
 80 |         if os.path.isdir(os.path.join(src_root, f))
 81 |         and not f.startswith("_")
 82 |         and not f.startswith(".")
 83 |     ]
 84 |     package_index_rst_path = os.path.join(
 85 |         rst_root,
 86 |         "index.rst",
 87 |     )
 88 |     log.info(f"Writing {package_index_rst_path}")
 89 |     write_to_file(
 90 |         index_template(
 91 |             base_package_name,
 92 |             doc_references=module_names + subdir_refs,
 93 |             text_prefix=_SUBTITLE,
 94 |         ),
 95 |         package_index_rst_path,
 96 |     )
 97 | 
 98 |     for root, dirnames, filenames in os.walk(src_root):
 99 |         if os.path.basename(root).startswith("_"):
100 |             continue
101 |         base_package_relpath = os.path.relpath(root, start=src_root)
102 |         base_package_qualname = package_prefix + os.path.relpath(
103 |             root,
104 |             start=os.path.dirname(src_root),
105 |         ).replace(os.path.sep, ".")
106 | 
107 |         for dirname in dirnames:
108 |             if dirname.startswith("_"):
109 |                 log.debug(f"Skipping {dirname}")
110 |                 continue
111 |             files_in_dir = os.listdir(os.path.join(root, dirname))
112 |             module_names = [
113 |                 f[:-3] for f in files_in_dir if f.endswith(".py") and not f.startswith("_")
114 |             ]
115 |             subdir_refs = [
116 |                 f"{f}/index"
117 |                 for f in files_in_dir
118 |                 if os.path.isdir(os.path.join(root, dirname, f)) and not f.startswith("_")
119 |             ]
120 |             package_qualname = f"{base_package_qualname}.{dirname}"
121 |             package_index_rst_path = os.path.join(
122 |                 rst_root,
123 |                 base_package_relpath,
124 |                 dirname,
125 |                 "index.rst",
126 |             )
127 |             log.info(f"Writing {package_index_rst_path}")
128 |             write_to_file(
129 |                 index_template(package_qualname, doc_references=module_names + subdir_refs),
130 |                 package_index_rst_path,
131 |             )
132 | 
133 |         for filename in filenames:
134 |             base_name, ext = os.path.splitext(filename)
135 |             if ext == ".py" and not filename.startswith("_"):
136 |                 module_qualname = f"{base_package_qualname}.{filename[:-3]}"
137 | 
138 |                 module_rst_path = os.path.join(rst_root, base_package_relpath, f"{base_name}.rst")
139 |                 if os.path.exists(module_rst_path) and not overwrite:
140 |                     log.debug(f"{module_rst_path} already exists, skipping it")
141 | 
142 |                 log.info(f"Writing module documentation to {module_rst_path}")
143 |                 write_to_file(module_template(module_qualname), module_rst_path)
144 | 
145 | 
146 | if __name__ == "__main__":
147 |     logging.basicConfig(level=logging.INFO)
148 |     docs_root = Path(__file__).parent
149 |     enable_module_docs = False
150 |     if enable_module_docs:
151 |         make_rst(
152 |             docs_root / ".." / "src" / "serena",
153 |             docs_root / "serena",
154 |             clean=True,
155 |         )
156 | 
```

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

```python
  1 | """
  2 | Basic integration tests for the MATLAB language server functionality.
  3 | 
  4 | These tests validate the functionality of the language server APIs
  5 | like request_document_symbols using the MATLAB test repository.
  6 | 
  7 | Requirements:
  8 |     - MATLAB R2021b or later must be installed
  9 |     - MATLAB_PATH environment variable should be set to MATLAB installation directory
 10 |     - Node.js and npm must be installed
 11 | """
 12 | 
 13 | import os
 14 | 
 15 | import pytest
 16 | 
 17 | from solidlsp import SolidLanguageServer
 18 | from solidlsp.ls_config import Language
 19 | 
 20 | # Skip all tests if MATLAB is not available
 21 | pytestmark = pytest.mark.matlab
 22 | 
 23 | # Check if MATLAB is available
 24 | MATLAB_AVAILABLE = os.environ.get("MATLAB_PATH") is not None or any(
 25 |     os.path.exists(p)
 26 |     for p in [
 27 |         "/Applications/MATLAB_R2024b.app",
 28 |         "/Applications/MATLAB_R2025b.app",
 29 |         "/Volumes/S1/Applications/MATLAB_R2024b.app",
 30 |         "/Volumes/S1/Applications/MATLAB_R2025b.app",
 31 |     ]
 32 | )
 33 | 
 34 | 
 35 | @pytest.mark.skipif(not MATLAB_AVAILABLE, reason="MATLAB installation not found")
 36 | class TestMatlabLanguageServerBasics:
 37 |     """Test basic functionality of the MATLAB language server."""
 38 | 
 39 |     @pytest.mark.parametrize("language_server", [Language.MATLAB], indirect=True)
 40 |     def test_matlab_language_server_initialization(self, language_server: SolidLanguageServer) -> None:
 41 |         """Test that MATLAB language server can be initialized successfully."""
 42 |         assert language_server is not None
 43 |         assert language_server.language == Language.MATLAB
 44 | 
 45 |     @pytest.mark.parametrize("language_server", [Language.MATLAB], indirect=True)
 46 |     def test_matlab_request_document_symbols_class(self, language_server: SolidLanguageServer) -> None:
 47 |         """Test request_document_symbols for MATLAB class file."""
 48 |         # Test getting symbols from Calculator.m (class file)
 49 |         all_symbols, _root_symbols = language_server.request_document_symbols("Calculator.m").get_all_symbols_and_roots()
 50 | 
 51 |         # Extract class symbols (LSP Symbol Kind 5 for class)
 52 |         class_symbols = [symbol for symbol in all_symbols if symbol.get("kind") == 5]
 53 |         class_names = [symbol["name"] for symbol in class_symbols]
 54 | 
 55 |         # Should find the Calculator class
 56 |         assert "Calculator" in class_names, "Should find Calculator class"
 57 | 
 58 |         # Extract method symbols (LSP Symbol Kind 6 for method or 12 for function)
 59 |         method_symbols = [symbol for symbol in all_symbols if symbol.get("kind") in [6, 12]]
 60 |         method_names = [symbol["name"] for symbol in method_symbols]
 61 | 
 62 |         # Should find key methods
 63 |         expected_methods = ["add", "subtract", "multiply", "divide"]
 64 |         for method in expected_methods:
 65 |             assert method in method_names, f"Should find {method} method in Calculator class"
 66 | 
 67 |     @pytest.mark.parametrize("language_server", [Language.MATLAB], indirect=True)
 68 |     def test_matlab_request_document_symbols_function(self, language_server: SolidLanguageServer) -> None:
 69 |         """Test request_document_symbols for MATLAB function file."""
 70 |         # Test getting symbols from lib/mathUtils.m (function file)
 71 |         all_symbols, _root_symbols = language_server.request_document_symbols("lib/mathUtils.m").get_all_symbols_and_roots()
 72 | 
 73 |         # Extract function symbols (LSP Symbol Kind 12 for function)
 74 |         function_symbols = [symbol for symbol in all_symbols if symbol.get("kind") == 12]
 75 |         function_names = [symbol["name"] for symbol in function_symbols]
 76 | 
 77 |         # Should find the main mathUtils function
 78 |         assert "mathUtils" in function_names, "Should find mathUtils function"
 79 | 
 80 |         # Should also find nested/local functions
 81 |         expected_local_functions = ["computeFactorial", "computeFibonacci", "checkPrime", "computeStats"]
 82 |         for func in expected_local_functions:
 83 |             assert func in function_names, f"Should find {func} local function"
 84 | 
 85 |     @pytest.mark.parametrize("language_server", [Language.MATLAB], indirect=True)
 86 |     def test_matlab_request_document_symbols_script(self, language_server: SolidLanguageServer) -> None:
 87 |         """Test request_document_symbols for MATLAB script file."""
 88 |         # Test getting symbols from main.m (script file)
 89 |         all_symbols, _root_symbols = language_server.request_document_symbols("main.m").get_all_symbols_and_roots()
 90 | 
 91 |         # Scripts may have variables and sections, but less structured symbols
 92 |         # Just verify we can get symbols without errors
 93 |         assert all_symbols is not None
 94 | 
 95 | 
 96 | @pytest.mark.skipif(not MATLAB_AVAILABLE, reason="MATLAB installation not found")
 97 | class TestMatlabLanguageServerReferences:
 98 |     """Test find references functionality of the MATLAB language server."""
 99 | 
100 |     @pytest.mark.parametrize("language_server", [Language.MATLAB], indirect=True)
101 |     def test_matlab_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
102 |         """Test finding references within a single MATLAB file."""
103 |         # Find references to 'result' variable in Calculator.m
104 |         # This is a basic test to verify references work
105 |         references = language_server.request_references("Calculator.m", 25, 12)  # 'result' in add method
106 | 
107 |         # Should find at least the definition
108 |         assert references is not None
109 | 
110 |     @pytest.mark.parametrize("language_server", [Language.MATLAB], indirect=True)
111 |     def test_matlab_find_references_cross_file(self, language_server: SolidLanguageServer) -> None:
112 |         """Test finding references across MATLAB files."""
113 |         # Find references to Calculator class used in main.m
114 |         references = language_server.request_references("main.m", 11, 8)  # 'Calculator' reference
115 | 
116 |         # Should find references in both main.m and Calculator.m
117 |         assert references is not None
118 | 
```
Page 3/21FirstPrevNextLast