#
tokens: 49243/50000 49/410 files (page 2/17)
lines: off (toggle) GitHub
raw markdown copy
This is page 2 of 17. Use http://codebase.md/oraios/serena?lines=false&page={x} to view the full context.

# Directory Structure

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

# Files

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

```ruby
require '../services.rb'
require '../models.rb'

class UserStats
  attr_reader :user_count, :active_users, :last_updated

  def initialize
    @user_count = 0
    @active_users = 0
    @last_updated = Time.now
  end

  def update_stats(total, active)
    @user_count = total
    @active_users = active
    @last_updated = Time.now
  end

  def activity_ratio
    return 0.0 if @user_count == 0
    (@active_users.to_f / @user_count * 100).round(2)
  end

  def formatted_stats
    "Users: #{@user_count}, Active: #{@active_users} (#{activity_ratio}%)"
  end
end

class UserManager
  def initialize
    @service = Services::UserService.new
    @stats = UserStats.new
  end

  def create_user_with_tracking(id, name, email = nil)
    user = @service.create_user(id, name)
    user.email = email if email
    
    update_statistics
    notify_user_created(user)
    
    user
  end

  def get_user_details(id)
    user = @service.get_user(id)
    return nil unless user
    
    {
      user_info: user.full_info,
      created_at: Time.now,
      stats: @stats.formatted_stats
    }
  end

  def bulk_create_users(user_data_list)
    created_users = []
    
    user_data_list.each do |data|
      user = create_user_with_tracking(data[:id], data[:name], data[:email])
      created_users << user
    end
    
    created_users
  end

  private

  def update_statistics
    total_users = @service.users.length
    # For demo purposes, assume all users are active
    @stats.update_stats(total_users, total_users)
  end

  def notify_user_created(user)
    puts "User created: #{user.name} (ID: #{user.id})"
  end
end

def process_user_data(raw_data)
  processed = raw_data.map do |entry|
    {
      id: entry["id"] || entry[:id],
      name: entry["name"] || entry[:name],
      email: entry["email"] || entry[:email]
    }
  end
  
  processed.reject { |entry| entry[:name].nil? || entry[:name].empty? }
end

def main
  # Example usage
  manager = UserManager.new
  
  sample_data = [
    { id: 1, name: "Alice Johnson", email: "[email protected]" },
    { id: 2, name: "Bob Smith", email: "[email protected]" },
    { id: 3, name: "Charlie Brown" }
  ]
  
  users = manager.bulk_create_users(sample_data)
  
  users.each do |user|
    details = manager.get_user_details(user.id)
    puts details[:user_info]
  end
  
  puts "\nFinal statistics:"
  stats = UserStats.new
  stats.update_stats(users.length, users.length)
  puts stats.formatted_stats
end

# Execute if this file is run directly
main if __FILE__ == $0
```

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

```
# Main script demonstrating various PowerShell features

# Import utility functions
. "$PSScriptRoot\utils.ps1"

# Global variables
$Script:ScriptName = "Main Script"
$Script:Counter = 0

<#
.SYNOPSIS
    Greets a user with various greeting styles.
.PARAMETER Username
    The name of the user to greet.
.PARAMETER GreetingType
    The type of greeting (formal, casual, or default).
#>
function Greet-User {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Username,

        [Parameter(Mandatory = $false)]
        [ValidateSet("formal", "casual", "default")]
        [string]$GreetingType = "default"
    )

    switch ($GreetingType) {
        "formal" {
            Write-Output "Good day, $Username!"
        }
        "casual" {
            Write-Output "Hey $Username!"
        }
        default {
            Write-Output "Hello, $Username!"
        }
    }
}

<#
.SYNOPSIS
    Processes an array of items with the specified operation.
.PARAMETER Items
    The array of items to process.
.PARAMETER Operation
    The operation to perform (count, uppercase).
#>
function Process-Items {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string[]]$Items,

        [Parameter(Mandatory = $true)]
        [ValidateSet("count", "uppercase")]
        [string]$Operation
    )

    foreach ($item in $Items) {
        switch ($Operation) {
            "count" {
                $Script:Counter++
                Write-Output "Processing item $($Script:Counter): $item"
            }
            "uppercase" {
                Write-Output $item.ToUpper()
            }
        }
    }
}

<#
.SYNOPSIS
    Main entry point for the script.
#>
function Main {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)]
        [string]$User = "World",

        [Parameter(Mandatory = $false)]
        [string]$Greeting = "default"
    )

    Write-Output "Starting $Script:ScriptName"

    # Use the Greet-User function
    Greet-User -Username $User -GreetingType $Greeting

    # Process some items
    $items = @("item1", "item2", "item3")
    Write-Output "Processing items..."
    Process-Items -Items $items -Operation "count"

    # Use utility functions from utils.ps1
    $upperName = Convert-ToUpperCase -InputString $User
    Write-Output "Uppercase name: $upperName"

    $trimmed = Remove-Whitespace -InputString "  Hello World  "
    Write-Output "Trimmed: '$trimmed'"

    Write-Output "Script completed successfully"
}

# Run main function
Main @args

```

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

```typescript
import { ref, computed, watch, inject, provide, type InjectionKey, type Ref } from 'vue'

/**
 * Theme configuration type
 */
export interface ThemeConfig {
  isDark: boolean
  primaryColor: string
  fontSize: number
}

/**
 * Injection key for theme - demonstrates provide/inject pattern
 */
export const ThemeKey: InjectionKey<Ref<ThemeConfig>> = Symbol('theme')

/**
 * Composable for theme management with watchers.
 * Demonstrates: watch, provide/inject, localStorage interaction
 */
export function useThemeProvider() {
  // Initialize theme from localStorage or defaults
  const loadThemeFromStorage = (): ThemeConfig => {
    const stored = localStorage.getItem('app-theme')
    if (stored) {
      try {
        return JSON.parse(stored)
      } catch {
        // Fall through to defaults
      }
    }
    return {
      isDark: false,
      primaryColor: '#667eea',
      fontSize: 16
    }
  }

  const theme = ref<ThemeConfig>(loadThemeFromStorage())

  // Computed properties
  const isDarkMode = computed(() => theme.value.isDark)
  const themeClass = computed(() => theme.value.isDark ? 'dark-theme' : 'light-theme')

  // Watch for theme changes and persist to localStorage
  watch(
    theme,
    (newTheme) => {
      localStorage.setItem('app-theme', JSON.stringify(newTheme))
      document.documentElement.className = newTheme.isDark ? 'dark' : 'light'
    },
    { deep: true }
  )

  // Methods
  const toggleDarkMode = (): void => {
    theme.value.isDark = !theme.value.isDark
  }

  const setPrimaryColor = (color: string): void => {
    theme.value.primaryColor = color
  }

  const setFontSize = (size: number): void => {
    if (size >= 12 && size <= 24) {
      theme.value.fontSize = size
    }
  }

  const resetTheme = (): void => {
    theme.value = {
      isDark: false,
      primaryColor: '#667eea',
      fontSize: 16
    }
  }

  // Provide theme to child components
  provide(ThemeKey, theme)

  return {
    theme,
    isDarkMode,
    themeClass,
    toggleDarkMode,
    setPrimaryColor,
    setFontSize,
    resetTheme
  }
}

/**
 * Composable for consuming theme in child components.
 * Demonstrates: inject pattern
 */
export function useTheme() {
  const theme = inject(ThemeKey)

  if (!theme) {
    throw new Error('useTheme must be used within a component that provides ThemeKey')
  }

  const isDark = computed(() => theme.value.isDark)
  const primaryColor = computed(() => theme.value.primaryColor)
  const fontSize = computed(() => theme.value.fontSize)

  return {
    theme,
    isDark,
    primaryColor,
    fontSize
  }
}

```

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

```dockerfile
# Base stage with common dependencies
FROM python:3.11-slim AS base
SHELL ["/bin/bash", "-c"]

# Set environment variables to make Python print directly to the terminal and avoid .pyc files.
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1

# Install system dependencies required for package manager and build tools.
# sudo, wget, zip needed for some assistants, like junie
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    build-essential \
    git \
    ssh \
    sudo \
    wget \
    zip \
    unzip \
    git \
    && rm -rf /var/lib/apt/lists/*

# Install pipx.
RUN python3 -m pip install --no-cache-dir pipx \
    && pipx ensurepath

# Install nodejs
ENV NVM_VERSION=0.40.3
ENV NODE_VERSION=22.18.0
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v${NVM_VERSION}/install.sh | bash
# standard location
ENV NVM_DIR=/root/.nvm
RUN . "$NVM_DIR/nvm.sh" && nvm install ${NODE_VERSION}
RUN . "$NVM_DIR/nvm.sh" && nvm use v${NODE_VERSION}
RUN . "$NVM_DIR/nvm.sh" && nvm alias default v${NODE_VERSION}
ENV PATH="${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH}"

# Add local bin to the path
ENV PATH="${PATH}:/root/.local/bin"

# Install the latest version of uv
RUN curl -LsSf https://astral.sh/uv/install.sh | sh

# Install Rust and rustup for rust-analyzer support (minimal profile)
ENV RUSTUP_HOME=/usr/local/rustup
ENV CARGO_HOME=/usr/local/cargo
ENV PATH="${CARGO_HOME}/bin:${PATH}"
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
    --default-toolchain stable \
    --profile minimal \
    && rustup component add rust-analyzer

# Set the working directory
WORKDIR /workspaces/serena

# Copy all files for development
COPY . /workspaces/serena/

# Install sed
RUN apt-get update && apt-get install -y sed

# Create Serena configuration
ENV SERENA_HOME=/workspaces/serena/config
RUN mkdir -p $SERENA_HOME
RUN cp src/serena/resources/serena_config.template.yml $SERENA_HOME/serena_config.yml
RUN sed -i 's/^gui_log_window: .*/gui_log_window: False/' $SERENA_HOME/serena_config.yml
RUN sed -i 's/^web_dashboard_listen_address: .*/web_dashboard_listen_address: 0.0.0.0/' $SERENA_HOME/serena_config.yml
RUN sed -i 's/^web_dashboard_open_on_launch: .*/web_dashboard_open_on_launch: False/' $SERENA_HOME/serena_config.yml

# Create virtual environment and install dependencies
RUN uv venv
RUN . .venv/bin/activate
RUN uv pip install -r pyproject.toml -e .
ENV PATH="/workspaces/serena/.venv/bin:${PATH}"

# Entrypoint to ensure environment is activated
ENTRYPOINT ["/bin/bash", "-c", "source .venv/bin/activate && $0 $@"]

```

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

```yaml
description: All tools, with detailed instructions for code editing
prompt: |
  You are operating in editing mode. You can edit files with the provided tools.
  You adhere to the project's code style and patterns.
  
  Use symbolic editing tools whenever possible for precise code modifications.
  If no explicit editing task has yet been provided, wait for the user to provide one. Do not be overly eager.

  When writing new code, think about where it belongs best. Don't generate new files if you don't plan on actually
  properly integrating them into the codebase.

  You have two main approaches for editing code: (a) editing at the symbol level and (b) file-based editing.
  The symbol-based approach is appropriate if you need to adjust an entire symbol, e.g. a method, a class, a function, etc.
  It is not appropriate if you need to adjust just a few lines of code within a larger symbol.

  **Symbolic editing**
  Use symbolic retrieval tools to identify the symbols you need to edit.
  If you need to replace the definition of a symbol, use the `replace_symbol_body` tool.
  If you want to add some new code at the end of the file, use the `insert_after_symbol` tool with the last top-level symbol in the file. 
  Similarly, you can use `insert_before_symbol` with the first top-level symbol in the file to insert code at the beginning of a file.
  You can understand relationships between symbols by using the `find_referencing_symbols` tool. If not explicitly requested otherwise by the user,
  you make sure that when you edit a symbol, the change is either backward-compatible or you find and update all references as needed.
  The `find_referencing_symbols` tool will give you code snippets around the references as well as symbolic information.
  You can assume that all symbol editing tools are reliable, so you never need to verify the results if the tools return without error.

  {% if 'replace_content' in available_tools %}
  **File-based editing**
  The `replace_content` tool allows you to perform regex-based replacements within files (as well as simple string replacements).
  This is your primary tool for editing code whenever replacing or deleting a whole symbol would be a more expensive operation,
  e.g. if you need to adjust just a few lines of code within a method.
  You are extremely good at regex, so you never need to check whether the replacement produced the correct result.
  In particular, you know how to use wildcards effectively in order to avoid specifying the full original text to be replaced!
  {% endif %}
excluded_tools:
 - replace_lines
 - insert_at_line
 - delete_lines

```

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

```markdown
# Custom Agents with Serena

As a reference implementation, we provide an integration with the [Agno](https://docs.agno.com/introduction/playground) agent framework.
Agno is a model-agnostic agent framework that allows you to turn Serena into an agent 
(independent of the MCP technology) with a large number of underlying LLMs. While Agno has recently
added support for MCP servers out of the box, our Agno integration predates this and is a good illustration of how
easy it is to integrate Serena into an arbitrary agent framework.

Here's how it works:

1. Download the agent-ui code with npx
   ```shell
   npx create-agent-ui@latest
   ```
   or, alternatively, clone it manually:
   ```shell
   git clone https://github.com/agno-agi/agent-ui.git
   cd agent-ui 
   pnpm install 
   pnpm dev
   ```

2. Install serena with the optional requirements:
   ```shell
   # You can also only select agno,google or agno,anthropic instead of all-extras
   uv pip install --all-extras -r pyproject.toml -e .
   ```
   
3. Copy `.env.example` to `.env` and fill in the API keys for the provider(s) you
   intend to use.

4. Start the agno agent app with
   ```shell
   uv run python scripts/agno_agent.py
   ```
   By default, the script uses Claude as the model, but you can choose any model
   supported by Agno (which is essentially any existing model).

5. In a new terminal, start the agno UI with
   ```shell
   cd agent-ui 
   pnpm dev
   ```
   Connect the UI to the agent you started above and start chatting. You will have
   the same tools as in the MCP server version.


Here is a short demo of Serena performing a small analysis task with the newest Gemini model:

https://github.com/user-attachments/assets/ccfcb968-277d-4ca9-af7f-b84578858c62


⚠️ IMPORTANT: In contrast to the MCP server approach, tool execution in the Agno UI does
not ask for the user's permission. The shell tool is particularly critical, as it can perform arbitrary code execution. 
While we have never encountered any issues with
this in our testing with Claude, allowing this may not be entirely safe. 
You may choose to disable certain tools for your setup in your Serena project's
configuration file (`.yml`).


## Other Agent Frameworks

It should be straightforward to incorporate Serena into any
agent framework (like [pydantic-ai](https://ai.pydantic.dev/), [langgraph](https://langchain-ai.github.io/langgraph/tutorials/introduction/) or others).
Typically, you need only to write an adapter for Serena's tools to the tool representation in the framework of your choice, 
as was done by us for Agno with `SerenaAgnoToolkit` (see `/src/serena/agno.py`).


```

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

```python
from collections.abc import Generator
from pathlib import Path

import pytest

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

# This mark will be applied to all tests in this module
pytestmark = pytest.mark.python


@pytest.fixture(scope="module")
def ls_with_ignored_dirs() -> Generator[SolidLanguageServer, None, None]:
    """Fixture to set up an LS for the python test repo with the 'scripts' directory ignored."""
    ignored_paths = ["scripts", "custom_test"]
    with start_ls_context(language=Language.PYTHON, ignored_paths=ignored_paths) as ls:
        yield ls


@pytest.mark.parametrize("ls_with_ignored_dirs", [Language.PYTHON], indirect=True)
def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
    """Tests that request_full_symbol_tree ignores the configured directory."""
    root = ls_with_ignored_dirs.request_full_symbol_tree()[0]
    root_children = root["children"]
    children_names = {child["name"] for child in root_children}
    assert children_names == {"test_repo", "examples"}


@pytest.mark.parametrize("ls_with_ignored_dirs", [Language.PYTHON], indirect=True)
def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
    """Tests that find_references ignores the configured directory."""
    # Location of Item, which is referenced in scripts
    definition_file = "test_repo/models.py"
    definition_line = 56
    definition_col = 6

    references = ls_with_ignored_dirs.request_references(definition_file, definition_line, definition_col)

    # assert that scripts does not appear in the references
    assert not any("scripts" in ref["relativePath"] for ref in references)


@pytest.mark.parametrize("repo_path", [Language.PYTHON], indirect=True)
def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
    """Tests that refs and symbols with glob patterns are ignored."""
    ignored_paths = ["*ipts", "custom_t*"]
    with start_ls_context(language=Language.PYTHON, repo_path=str(repo_path), ignored_paths=ignored_paths) as ls:
        # same as in the above tests
        root = ls.request_full_symbol_tree()[0]
        root_children = root["children"]
        children_names = {child["name"] for child in root_children}
        assert children_names == {"test_repo", "examples"}

        # test that the refs and symbols with glob patterns are ignored
        definition_file = "test_repo/models.py"
        definition_line = 56
        definition_col = 6

        references = ls.request_references(definition_file, definition_line, definition_col)
        assert not any("scripts" in ref["relativePath"] for ref in references)

```

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

```python
"""
Defines settings for Solid-LSP
"""

import logging
import os
import pathlib
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any

from sensai.util.string import ToStringMixin

if TYPE_CHECKING:
    from solidlsp.ls_config import Language

log = logging.getLogger(__name__)


@dataclass
class SolidLSPSettings:
    solidlsp_dir: str = str(pathlib.Path.home() / ".solidlsp")
    """
    Path to the directory in which to store global Solid-LSP data (which is not project-specific)
    """
    project_data_relative_path: str = ".solidlsp"
    """
    Relative path within each project directory where Solid-LSP can store project-specific data, e.g. cache files.
    For instance, if this is ".solidlsp" and the project is located at "/home/user/myproject",
    then Solid-LSP will store project-specific data in "/home/user/myproject/.solidlsp".
    """
    ls_specific_settings: dict["Language", dict[str, Any]] = field(default_factory=dict)
    """
    Advanced configuration option allowing to configure language server implementation specific options.
    Have a look at the docstring of the constructors of the corresponding LS implementations within solidlsp to see which options are available.
    No documentation on options means no options are available.
    """

    def __post_init__(self) -> None:
        os.makedirs(str(self.solidlsp_dir), exist_ok=True)
        os.makedirs(str(self.ls_resources_dir), exist_ok=True)

    @property
    def ls_resources_dir(self) -> str:
        return os.path.join(str(self.solidlsp_dir), "language_servers", "static")

    class CustomLSSettings(ToStringMixin):
        def __init__(self, settings: dict[str, Any] | None) -> None:
            self.settings = settings or {}

        def get(self, key: str, default_value: Any = None) -> Any:
            """
            Returns the custom setting for the given key or the default value if not set.
            If a custom value is set for the given key, the retrieval is logged.

            :param key: the key
            :param default_value: the default value to use if no custom value is set
            :return: the value
            """
            if key in self.settings:
                value = self.settings[key]
                log.info("Using custom LS setting %s for key '%s'", value, key)
            else:
                value = default_value
            return value

    def get_ls_specific_settings(self, language: "Language") -> CustomLSSettings:
        """
        Get the language server specific settings for the given language.

        :param language: The programming language.
        :return: A dictionary of settings for the language server.
        """
        return self.CustomLSSettings(self.ls_specific_settings.get(language))

```

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

```markdown
# The Serena JetBrains Plugin

The [JetBrains Plugin](https://plugins.jetbrains.com/plugin/28946-serena/) allows Serena to
leverage the powerful code analysis and editing capabilities of your JetBrains IDE.

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

We recommend the JetBrains plugin as the preferred way of using Serena,
especially for users of JetBrains IDEs.

**Purchasing the JetBrains Plugin supports the Serena project.**
The proceeds from plugin sales allow us to dedicate more resources to further developing and improving Serena.

## Configuring Serena 

After installing the plugin, you need to configure Serena to use it.

**Central Configuration**.

Edit the global Serena configuration file located at `~/.serena/serena_config.yml` 
(`%USERPROFILE%\.serena\serena_config.yml` on Windows).
Change the `language_backend` setting as follows:

```yaml
language_backend: JetBrains
```

*Note*: you can also use the button `Edit Global Serena Config` in the Serena MCP dashboard to open the config file in your default editor.

**Per-Instance Configuration**.
The configuration setting in the global config file can be overridden on a 
per-instance basis by providing the arguments `--language-backend JetBrains` when 
launching the Serena MCP server.

**Verifying the Setup**.
You can verify that Serena is using the JetBrains plugin by either checking the dashboard, where
you will see `Languages:
Using JetBrains backend` in the configuration overview.
You will also notice that your client will use the JetBrains-specific tools like `jet_brains_find_symbol` and others like it.


## Advantages of the JetBrains Plugin

There are multiple features that are only available when using the JetBrains plugin:

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

We are also working on additional features like a `move_symbol` tool and debugging-related capabilities that
will be available exclusively through the JetBrains plugin.

## Usage with Other Editors

We realize that not everyone uses a JetBrains IDE as their main code editor.
You can still take advantage of the JetBrains plugin by running a JetBrains IDE instance alongside your
preferred editor. Most JetBrains IDEs have a free community edition that you can use for this purpose.
You just need to make sure that the project you are working on is open and indexed in the JetBrains IDE, 
so that Serena can connect to it.

```

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

```python
import os

import pytest

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


@pytest.mark.kotlin
class TestKotlinLanguageServer:
    @pytest.mark.parametrize("language_server", [Language.KOTLIN], indirect=True)
    def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
        symbols = language_server.request_full_symbol_tree()
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main class not found in symbol tree"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils class not found in symbol tree"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model class not found in symbol tree"

    @pytest.mark.parametrize("language_server", [Language.KOTLIN], indirect=True)
    def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
        # Use correct Kotlin file paths
        file_path = os.path.join("src", "main", "kotlin", "test_repo", "Utils.kt")
        refs = language_server.request_references(file_path, 3, 12)
        assert any("Main.kt" in ref.get("relativePath", "") for ref in refs), "Main should reference Utils.printHello"

        # Dynamically determine the correct line/column for the 'Model' class name
        file_path = os.path.join("src", "main", "kotlin", "test_repo", "Model.kt")
        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
        model_symbol = None
        for sym in symbols[0]:
            print(sym)
            print("\n")
            if sym.get("name") == "Model" and sym.get("kind") == 23:  # 23 = Class
                model_symbol = sym
                break
        assert model_symbol is not None, "Could not find 'Model' class symbol in Model.kt"
        # Use selectionRange if present, otherwise fall back to range
        if "selectionRange" in model_symbol:
            sel_start = model_symbol["selectionRange"]["start"]
        else:
            sel_start = model_symbol["range"]["start"]
        refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
        assert any(
            "Main.kt" in ref.get("relativePath", "") for ref in refs
        ), "Main should reference Model (tried all positions in selectionRange)"

    @pytest.mark.parametrize("language_server", [Language.KOTLIN], indirect=True)
    def test_overview_methods(self, language_server: SolidLanguageServer) -> None:
        symbols = language_server.request_full_symbol_tree()
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main missing from overview"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils missing from overview"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model missing from overview"

```

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

```python
"""
Test module for variable declarations and usage.

This module tests various types of variable declarations and usages including:
- Module-level variables
- Class-level variables
- Instance variables
- Variable reassignments
"""

from dataclasses import dataclass, field

# Module-level variables
module_var = "Initial module value"

reassignable_module_var = 10
reassignable_module_var = 20  # Reassigned

# Module-level variable with type annotation
typed_module_var: int = 42


# Regular class with class and instance variables
class VariableContainer:
    """Class that contains various variables."""

    # Class-level variables
    class_var = "Initial class value"

    reassignable_class_var = True
    reassignable_class_var = False  # Reassigned #noqa: PIE794

    # Class-level variable with type annotation
    typed_class_var: str = "typed value"

    def __init__(self):
        # Instance variables
        self.instance_var = "Initial instance value"
        self.reassignable_instance_var = 100

        # Instance variable with type annotation
        self.typed_instance_var: list[str] = ["item1", "item2"]

    def modify_instance_var(self):
        # Reassign instance variable
        self.instance_var = "Modified instance value"
        self.reassignable_instance_var = 200  # Reassigned

    def use_module_var(self):
        # Use module-level variables
        result = module_var + " used in method"
        other_result = reassignable_module_var + 5
        return result, other_result

    def use_class_var(self):
        # Use class-level variables
        result = VariableContainer.class_var + " used in method"
        other_result = VariableContainer.reassignable_class_var
        return result, other_result


# Dataclass with variables
@dataclass
class VariableDataclass:
    """Dataclass that contains various fields."""

    # Field variables with type annotations
    id: int
    name: str
    items: list[str] = field(default_factory=list)
    metadata: dict[str, str] = field(default_factory=dict)
    optional_value: float | None = None

    # This will be reassigned in various places
    status: str = "pending"


# Function that uses the module variables
def use_module_variables():
    """Function that uses module-level variables."""
    result = module_var + " used in function"
    other_result = reassignable_module_var * 2
    return result, other_result


# Create instances and use variables
dataclass_instance = VariableDataclass(id=1, name="Test")
dataclass_instance.status = "active"  # Reassign dataclass field

# Use variables at module level
module_result = module_var + " used at module level"
other_module_result = reassignable_module_var + 30

# Create a second dataclass instance with different status
second_dataclass = VariableDataclass(id=2, name="Another Test")
second_dataclass.status = "completed"  # Another reassignment of status

```

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

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

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

import pytest

from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language


@pytest.mark.terraform
class TestLanguageServerBasics:
    """Test basic functionality of the Terraform language server."""

    @pytest.mark.parametrize("language_server", [Language.TERRAFORM], indirect=True)
    def test_basic_definition(self, language_server: SolidLanguageServer) -> None:
        """Test basic definition lookup functionality."""
        # Simple test to verify the language server is working
        file_path = "main.tf"
        # Just try to get document symbols - this should work without hanging
        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
        assert len(symbols) > 0, "Should find at least some symbols in main.tf"

    @pytest.mark.parametrize("language_server", [Language.TERRAFORM], indirect=True)
    def test_request_references_aws_instance(self, language_server: SolidLanguageServer) -> None:
        """Test request_references on an aws_instance resource."""
        # Get references to an aws_instance resource in main.tf
        file_path = "main.tf"
        # Find aws_instance resources
        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
        aws_instance_symbol = next((s for s in symbols[0] if s.get("name") == 'resource "aws_instance" "web_server"'), None)
        if not aws_instance_symbol or "selectionRange" not in aws_instance_symbol:
            raise AssertionError("aws_instance symbol or its selectionRange not found")
        sel_start = aws_instance_symbol["selectionRange"]["start"]
        references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
        assert len(references) >= 1, "aws_instance should be referenced at least once"

    @pytest.mark.parametrize("language_server", [Language.TERRAFORM], indirect=True)
    def test_request_references_variable(self, language_server: SolidLanguageServer) -> None:
        """Test request_references on a variable."""
        # Get references to a variable in variables.tf
        file_path = "variables.tf"
        # Find variable definitions
        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
        var_symbol = next((s for s in symbols[0] if s.get("name") == 'variable "instance_type"'), None)
        if not var_symbol or "selectionRange" not in var_symbol:
            raise AssertionError("variable symbol or its selectionRange not found")
        sel_start = var_symbol["selectionRange"]["start"]
        references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
        assert len(references) >= 1, "variable should be referenced at least once"

```

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

```python
import os

import pytest

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

pytestmark = [pytest.mark.java, pytest.mark.skipif(not language_tests_enabled(Language.JAVA), reason="Java tests disabled")]


class TestJavaLanguageServer:
    @pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True)
    def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
        symbols = language_server.request_full_symbol_tree()
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main class not found in symbol tree"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils class not found in symbol tree"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model class not found in symbol tree"

    @pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True)
    def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
        # Use correct Maven/Java file paths
        file_path = os.path.join("src", "main", "java", "test_repo", "Utils.java")
        refs = language_server.request_references(file_path, 4, 20)
        assert any("Main.java" in ref.get("relativePath", "") for ref in refs), "Main should reference Utils.printHello"

        # Dynamically determine the correct line/column for the 'Model' class name
        file_path = os.path.join("src", "main", "java", "test_repo", "Model.java")
        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
        model_symbol = None
        for sym in symbols[0]:
            if sym.get("name") == "Model" and sym.get("kind") == 5:  # 5 = Class
                model_symbol = sym
                break
        assert model_symbol is not None, "Could not find 'Model' class symbol in Model.java"
        # Use selectionRange if present, otherwise fall back to range
        if "selectionRange" in model_symbol:
            sel_start = model_symbol["selectionRange"]["start"]
        else:
            sel_start = model_symbol["range"]["start"]
        refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
        assert any(
            "Main.java" in ref.get("relativePath", "") for ref in refs
        ), "Main should reference Model (tried all positions in selectionRange)"

    @pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True)
    def test_overview_methods(self, language_server: SolidLanguageServer) -> None:
        symbols = language_server.request_full_symbol_tree()
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main missing from overview"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils missing from overview"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model missing from overview"

```

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

```python
import os

import pytest

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


@pytest.mark.elm
class TestElmLanguageServer:
    @pytest.mark.parametrize("language_server", [Language.ELM], indirect=True)
    def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
        symbols = language_server.request_full_symbol_tree()
        assert SymbolUtils.symbol_tree_contains_name(symbols, "greet"), "greet function not found in symbol tree"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "calculateSum"), "calculateSum function not found in symbol tree"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "formatMessage"), "formatMessage function not found in symbol tree"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "addNumbers"), "addNumbers function not found in symbol tree"

    @pytest.mark.parametrize("language_server", [Language.ELM], indirect=True)
    def test_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
        file_path = os.path.join("Main.elm")
        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
        greet_symbol = None
        for sym in symbols[0]:
            if sym.get("name") == "greet":
                greet_symbol = sym
                break
        assert greet_symbol is not None, "Could not find 'greet' symbol in Main.elm"
        sel_start = greet_symbol["selectionRange"]["start"]
        refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
        assert any("Main.elm" in ref.get("relativePath", "") for ref in refs), "Main.elm should reference greet function"

    @pytest.mark.parametrize("language_server", [Language.ELM], indirect=True)
    def test_find_references_across_files(self, language_server: SolidLanguageServer) -> None:
        # Test formatMessage function which is defined in Utils.elm and used in Main.elm
        utils_path = os.path.join("Utils.elm")
        symbols = language_server.request_document_symbols(utils_path).get_all_symbols_and_roots()
        formatMessage_symbol = None
        for sym in symbols[0]:
            if sym.get("name") == "formatMessage":
                formatMessage_symbol = sym
                break
        assert formatMessage_symbol is not None, "Could not find 'formatMessage' symbol in Utils.elm"

        # Get references from the definition in Utils.elm
        sel_start = formatMessage_symbol["selectionRange"]["start"]
        refs = language_server.request_references(utils_path, sel_start["line"], sel_start["character"])

        # Verify that we found references
        assert refs, "Expected to find references for formatMessage"

        # Verify that at least one reference is in Main.elm (where formatMessage is used)
        assert any("Main.elm" in ref.get("relativePath", "") for ref in refs), "Expected to find usage of formatMessage in Main.elm"

```

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

```markdown
# Scala Setup Guide for Serena

This guide explains how to prepare a Scala project so that Serena can provide reliable code intelligence via Metals (Scala LSP) and how to run Scala tests manually.

Serena automatically bootstraps the Metals language server using Coursier when needed. Your project, however, must be importable by a build server (BSP) — typically via Bloop or sbt’s built‑in BSP — so that Metals can compile and index your code.

---
## Prerequisites

Install the following on your system and ensure they are available on `PATH`:

- Java Development Kit (JDK). A modern LTS (e.g., 17 or 21) is recommended.
- `sbt`
- Coursier command (`cs`) or the legacy `coursier` launcher
  - Serena uses `cs` if available; if only `coursier` exists, it will attempt to install `cs`. If neither is present, install Coursier first.

---
## Quick Start (Recommended: VS Code + Metals auto‑import)

1. Open your Scala project in VS Code.
2. When prompted by Metals, accept “Import build”. Wait until the import and initial compile/indexing finish.
3. Run the “Connect to build server” command (id: `build.connect`).
4. Once the import completes, start Serena in your project root and use it.

This flow ensures the `.bloop/` and (if applicable) `.metals/` directories are created and your build is known to the build server that Metals uses.

---
## Manual Setup (No VS Code)

Follow these steps if you prefer a manual setup or you are not using VS Code:

These instructions cover the setup for projects that use sbt as the build tool, with Bloop as the BSP server.


1. Add Bloop to `project/plugins.sbt` in your Scala project:
   ```scala
   // project/plugins.sbt
   addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "<version>")
   ```
   Replace `<version>` with an appropriate current version from the Metals documentation.

2. Export Bloop configuration with sources:
   ```bash
   sbt -Dbloop.export-jar-classifiers=sources bloopInstall
   ```
   This creates a `.bloop/` directory containing your project’s build metadata for the BSP server.

3. Compile from sbt to verify the build:
   ```bash
   sbt compile
   ```

4. Start Serena in your project root. Serena will bootstrap Metals (if not already present) and connect to the build server using the configuration exported above.

---
## Using Serena with Scala

- Serena automatically detects Scala files (`*.scala`, `*.sbt`) and will start a Metals process per project when needed.
- On first run, you may see messages like “Bootstrapping metals…” in the Serena logs — this is expected.
- Optimal results require that your project compiles successfully via the build server (BSP). If compilation fails, fix build errors in `sbt` first.


Notes:
- Ensure you completed the manual or auto‑import steps so that the build is compiled and indexed; otherwise, code navigation and references may be incomplete until the first successful compile.

## Reference 
- Metals + sbt: [https://scalameta.org/metals/docs/build-tools/sbt](https://scalameta.org/metals/docs/build-tools/sbt)
```

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

```python
import os

import pytest

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


@pytest.mark.rust
class TestRustLanguageServer:
    @pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
    def test_find_references_raw(self, language_server: SolidLanguageServer) -> None:
        # Directly test the request_references method for the add function
        file_path = os.path.join("src", "lib.rs")
        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
        add_symbol = None
        for sym in symbols[0]:
            if sym.get("name") == "add":
                add_symbol = sym
                break
        assert add_symbol is not None, "Could not find 'add' function symbol in lib.rs"
        sel_start = add_symbol["selectionRange"]["start"]
        refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
        assert any(
            "main.rs" in ref.get("relativePath", "") for ref in refs
        ), "main.rs should reference add (raw, tried all positions in selectionRange)"

    @pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
    def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
        symbols = language_server.request_full_symbol_tree()
        assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main function not found in symbol tree"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add function not found in symbol tree"
        # Add more as needed based on test_repo

    @pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
    def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
        # Find references to 'add' defined in lib.rs, should be referenced from main.rs
        file_path = os.path.join("src", "lib.rs")
        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
        add_symbol = None
        for sym in symbols[0]:
            if sym.get("name") == "add":
                add_symbol = sym
                break
        assert add_symbol is not None, "Could not find 'add' function symbol in lib.rs"
        sel_start = add_symbol["selectionRange"]["start"]
        refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
        assert any(
            "main.rs" in ref.get("relativePath", "") for ref in refs
        ), "main.rs should reference add (tried all positions in selectionRange)"

    @pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
    def test_overview_methods(self, language_server: SolidLanguageServer) -> None:
        symbols = language_server.request_full_symbol_tree()
        assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main missing from overview"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add missing from overview"

```

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

```markdown
# Groovy Setup Guide for Serena

The Groovy support in Serena is incomplete and requires the user to provide a functioning,
JVM-based Groovy language server as a jar. This intermediate state allows the contributors
of Groovy support to use Serena internally and hopefully to accelerate their open-source
release of a Groovy language server in the future.

If you happen to have a Groovy language server JAR file, you can configure Serena to use it
by following the instructions below.

---
## Prerequisites

- Groovy Language Server JAR file
    - Can be any open-source Groovy language server or your custom implementation
    - The JAR must be compatible with standard LSP protocol

---
## Configuration

Configure Groovy Language Server by adding settings to your `~/.serena/serena_config.yml`:

### Basic Configuration

```yaml
ls_specific_settings:
  groovy:
    ls_jar_path: '/path/to/groovy-language-server.jar'
    ls_jar_options: '-Xmx2G -Xms512m'
```

### Custom Java Paths

If you have specific Java installations:

```yaml
ls_specific_settings:
  groovy:
    ls_jar_path: '/path/to/groovy-language-server.jar'
    ls_java_home_path: '/usr/lib/jvm/java-21-openjdk'  # Custom JAVA_HOME directory
    ls_jar_options: '-Xmx2G -Xms512m'                  # Optional JVM options
```

### Configuration Options

- `ls_jar_path`: Absolute path to your Groovy Language Server JAR file (required)
- `ls_java_home_path`: Custom JAVA_HOME directory for Java installation (optional)
    - When specified, Serena will use this Java installation instead of downloading bundled Java
    - Java executable path is automatically determined based on platform:
        - Windows: `{ls_java_home_path}/bin/java.exe`
        - Linux/macOS: `{ls_java_home_path}/bin/java`
    - Validates that Java executable exists at the expected location
- `ls_jar_options`: JVM options for language server (optional)
    - Common options:
        - `-Xmx<size>`: Maximum heap size (e.g., `-Xmx2G` for 2GB)
        - `-Xms<size>`: Initial heap size (e.g., `-Xms512m` for 512MB)

---
## Project Structure Requirements

For optimal Groovy Language Server performance, ensure your project follows standard Groovy/Gradle structure:

```
project-root/
├── src/
│   ├── main/
│   │   ├── groovy/
│   │   └── resources/
│   └── test/
│       ├── groovy/
│       └── resources/
├── build.gradle or build.gradle.kts
├── settings.gradle or settings.gradle.kts
└── gradle/
    └── wrapper/
```

---
## Using Serena with Groovy

- Serena automatically detects Groovy files (`*.groovy`, `*.gvy`) and will start a Groovy Language Server JAR process per project when needed.
- Optimal results require that your project compiles successfully via Gradle or Maven. If compilation fails, fix build errors in your build tool first.

## Reference

- **Groovy Documentation**: [https://groovy-lang.org/documentation.html](https://groovy-lang.org/documentation.html)
- **Gradle Documentation**: [https://docs.gradle.org](https://docs.gradle.org)
- **Serena Configuration**: [../02-usage/050_configuration.md](../02-usage/050_configuration.md)
```

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

```markdown

# Connecting Serena MCP Server to ChatGPT via MCPO & Cloudflare Tunnel

This guide explains how to expose a **locally running Serena MCP server** (powered by MCPO) to the internet using **Cloudflare Tunnel**, and how to connect it to **ChatGPT as a Custom GPT with tool access**.

Once configured, ChatGPT becomes a powerful **coding agent** with direct access to your codebase, shell, and file system — so **read the security notes carefully**.

---
## Prerequisites

Make sure you have [uv](https://docs.astral.sh/uv/getting-started/installation/) 
and [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) installed.

## 1. Start the Serena MCP Server via MCPO

Run the following command to launch Serena as http server (assuming port 8000):

```bash
uvx mcpo --port 8000 --api-key <YOUR_SECRET_KEY> -- \
  uvx --from git+https://github.com/oraios/serena \
  serena start-mcp-server --context chatgpt --project $(pwd)
```

- `--api-key` is required to secure the server.
- `--project` should point to the root of your codebase.

You can also use other options, and you don't have to pass `--project` if you want to work on multiple projects
or want to activate it later. See 

```shell
uvx --from git+https://github.com/oraios/serena serena start-mcp-server --help
```

---

## 2. Expose the Server Using Cloudflare Tunnel

Run:

```bash
cloudflared tunnel --url http://localhost:8000
```

This will give you a **public HTTPS URL** like:

```
https://serena-agent-tunnel.trycloudflare.com
```

Your server is now securely exposed to the internet.

---

## 3. Connect It to ChatGPT (Custom GPT)

### Steps:

1. Go to [ChatGPT → Explore GPTs → Create](https://chat.openai.com/gpts/editor)
2. During setup, click **“Add APIs”**
3. Set up **API Key authentication** with the auth type as **Bearer** and enter the api key you used to start the MCPO server.
4. In the **Schema** section, click on **import from URL** and paste `<cloudflared_url>/openapi.json` with the URL you got from the previous step.
5. Add the following line to the top of the imported JSON schema:
    ```
     "servers": ["url": "<cloudflared_url>"],
    ```
   **Important**: don't include a trailing slash at the end of the URL!

ChatGPT will read the schema and create functions automatically.

---

## Security Warning — Read Carefully

Depending on your configuration and enabled tools, Serena's MCP server may:
- Execute **arbitrary shell commands**
- Read, write, and modify **files in your codebase**

This gives ChatGPT the same powers as a remote developer on your machine.

### ⚠️ Key Rules:
- **NEVER expose your API key**
- **Only expose this server when needed**, and monitor its use.

In your project’s `.serena/project.yml` or global config, you can disable tools like:

```yaml
excluded_tools:
  - execute_shell_command
  - ...
read_only: true
```

This is strongly recommended if you want a read-only or safer agent.


---

## Final Thoughts

With this setup, ChatGPT becomes a coding assistant **running on your local code** — able to index, search, edit, and even run shell commands depending on your configuration.

Use responsibly, and keep security in mind.

```

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

```ruby
require './models.rb'

# Global variables for testing references
$global_counter = 0
$global_config = {
  debug: true,
  timeout: 30
}

class DataContainer
  attr_accessor :status, :data, :metadata

  def initialize
    @status = "pending"
    @data = {}
    @metadata = {
      created_at: Time.now,
      version: "1.0"
    }
  end

  def update_status(new_status)
    old_status = @status
    @status = new_status
    log_status_change(old_status, new_status)
  end

  def process_data(input_data)
    @data = input_data
    @status = "processing"
    
    # Process the data
    result = @data.transform_values { |v| v.to_s.upcase }
    @status = "completed"
    
    result
  end

  def get_metadata_info
    info = "Status: #{@status}, Version: #{@metadata[:version]}"
    info += ", Created: #{@metadata[:created_at]}"
    info
  end

  private

  def log_status_change(old_status, new_status)
    puts "Status changed from #{old_status} to #{new_status}"
  end
end

class StatusTracker
  def initialize
    @tracked_items = []
  end

  def add_item(item)
    @tracked_items << item
    item.status = "tracked" if item.respond_to?(:status=)
  end

  def find_by_status(target_status)
    @tracked_items.select { |item| item.status == target_status }
  end

  def update_all_status(new_status)
    @tracked_items.each do |item|
      item.status = new_status if item.respond_to?(:status=)
    end
  end
end

# Module level variables and functions
module ProcessingHelper
  PROCESSING_MODES = ["sync", "async", "batch"].freeze
  
  @@instance_count = 0
  
  def self.create_processor(mode = "sync")
    @@instance_count += 1
    {
      id: @@instance_count,
      mode: mode,
      created_at: Time.now
    }
  end
  
  def self.get_instance_count
    @@instance_count
  end
end

# Test instances for reference testing
dataclass_instance = DataContainer.new
dataclass_instance.status = "initialized"

second_dataclass = DataContainer.new  
second_dataclass.update_status("ready")

tracker = StatusTracker.new
tracker.add_item(dataclass_instance)
tracker.add_item(second_dataclass)

# Function that uses the variables
def demonstrate_variable_usage
  puts "Global counter: #{$global_counter}"
  
  container = DataContainer.new
  container.status = "demo"
  
  processor = ProcessingHelper.create_processor("async")
  puts "Created processor #{processor[:id]} in #{processor[:mode]} mode"
  
  container
end

# More complex variable interactions
class VariableInteractionTest
  def initialize
    @internal_status = "created"
    @data_containers = []
  end
  
  def add_container(container)
    @data_containers << container
    container.status = "added_to_collection"
    @internal_status = "modified"
  end
  
  def process_all_containers
    @data_containers.each do |container|
      container.status = "batch_processed"
    end
    @internal_status = "processing_complete"
  end
  
  def get_status_summary
    statuses = @data_containers.map(&:status)
    {
      internal: @internal_status,
      containers: statuses,
      count: @data_containers.length
    }
  end
end

# Create instances for testing
interaction_test = VariableInteractionTest.new
interaction_test.add_container(dataclass_instance)
interaction_test.add_container(second_dataclass)
```

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

```python
from serena.config.context_mode import SerenaAgentMode
from serena.tools import Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional


class OpenDashboardTool(Tool, ToolMarkerOptional, ToolMarkerDoesNotRequireActiveProject):
    """
    Opens the Serena web dashboard in the default web browser.
    The dashboard provides logs, session information, and tool usage statistics.
    """

    def apply(self) -> str:
        """
        Opens the Serena web dashboard in the default web browser.
        """
        if self.agent.open_dashboard():
            return f"Serena web dashboard has been opened in the user's default web browser: {self.agent.get_dashboard_url()}"
        else:
            return f"Serena web dashboard could not be opened automatically; tell the user to open it via {self.agent.get_dashboard_url()}"


class ActivateProjectTool(Tool, ToolMarkerDoesNotRequireActiveProject):
    """
    Activates a project based on the project name or path.
    """

    def apply(self, project: str) -> str:
        """
        Activates the project with the given name or path.

        :param project: the name of a registered project to activate or a path to a project directory
        """
        active_project = self.agent.activate_project_from_path_or_name(project)
        result = active_project.get_activation_message()
        result += "\nIMPORTANT: If you have not yet read the 'Serena Instructions Manual', do it now before continuing!"
        return result


class RemoveProjectTool(Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional):
    """
    Removes a project from the Serena configuration.
    """

    def apply(self, project_name: str) -> str:
        """
        Removes a project from the Serena configuration.

        :param project_name: Name of the project to remove
        """
        self.agent.serena_config.remove_project(project_name)
        return f"Successfully removed project '{project_name}' from configuration."


class SwitchModesTool(Tool, ToolMarkerOptional):
    """
    Activates modes by providing a list of their names
    """

    def apply(self, modes: list[str]) -> str:
        """
        Activates the desired modes, like ["editing", "interactive"] or ["planning", "one-shot"]

        :param modes: the names of the modes to activate
        """
        mode_instances = [SerenaAgentMode.load(mode) for mode in modes]
        self.agent.set_modes(mode_instances)

        # Inform the Agent about the activated modes and the currently active tools
        result_str = f"Successfully activated modes: {', '.join([mode.name for mode in mode_instances])}" + "\n"
        result_str += "\n".join([mode_instance.prompt for mode_instance in mode_instances]) + "\n"
        result_str += f"Currently active tools: {', '.join(self.agent.get_active_tool_names())}"
        return result_str


class GetCurrentConfigTool(Tool):
    """
    Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
    """

    def apply(self) -> str:
        """
        Print the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
        """
        return self.agent.get_current_config_overview()

```

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

```python
"""
Tests for TOML language server directory ignoring functionality.

These tests validate that the Taplo language server correctly ignores
TOML-specific directories like target, .cargo, and node_modules.
"""

import pytest

from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language

pytestmark = pytest.mark.toml


@pytest.mark.parametrize("language_server", [Language.TOML], indirect=True)
class TestTomlIgnoredDirectories:
    """Test TOML-specific directory ignoring behavior."""

    def test_default_ignored_directories(self, language_server: SolidLanguageServer) -> None:
        """Test that default TOML directories are ignored."""
        # Test that TOML/Rust/Node-specific directories are ignored by default
        assert language_server.is_ignored_dirname("target"), "target should be ignored"
        assert language_server.is_ignored_dirname(".cargo"), ".cargo should be ignored"
        assert language_server.is_ignored_dirname("node_modules"), "node_modules should be ignored"

        # Directories starting with . are ignored by base class
        assert language_server.is_ignored_dirname(".git"), ".git should be ignored"
        assert language_server.is_ignored_dirname(".venv"), ".venv should be ignored"

    def test_important_directories_not_ignored(self, language_server: SolidLanguageServer) -> None:
        """Test that important directories are not ignored."""
        # Common project directories should not be ignored
        assert not language_server.is_ignored_dirname("src"), "src should not be ignored"
        assert not language_server.is_ignored_dirname("crates"), "crates should not be ignored"
        assert not language_server.is_ignored_dirname("lib"), "lib should not be ignored"
        assert not language_server.is_ignored_dirname("tests"), "tests should not be ignored"
        assert not language_server.is_ignored_dirname("config"), "config should not be ignored"

    def test_cargo_related_directories(self, language_server: SolidLanguageServer) -> None:
        """Test Cargo/Rust-related directory handling."""
        # Rust build directories should be ignored
        assert language_server.is_ignored_dirname("target"), "target (Rust build) should be ignored"
        assert language_server.is_ignored_dirname(".cargo"), ".cargo should be ignored"

        # But important Rust directories should not be ignored
        assert not language_server.is_ignored_dirname("benches"), "benches should not be ignored"
        assert not language_server.is_ignored_dirname("examples"), "examples should not be ignored"

    def test_various_cache_directories(self, language_server: SolidLanguageServer) -> None:
        """Test various cache and temporary directories are ignored."""
        # Directories starting with . are ignored by base class
        assert language_server.is_ignored_dirname(".cache"), ".cache should be ignored"

        # IDE directories (start with .)
        assert language_server.is_ignored_dirname(".idea"), ".idea should be ignored"
        assert language_server.is_ignored_dirname(".vscode"), ".vscode should be ignored"

        # Note: __pycache__ is NOT ignored by TOML server (only Python servers ignore it)
        assert not language_server.is_ignored_dirname("__pycache__"), "__pycache__ is not TOML-specific"

```

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

```python
"""
Utility functions and classes demonstrating various Python features.
"""

import logging
from collections.abc import Callable
from typing import Any, TypeVar

# Type variables for generic functions
T = TypeVar("T")
U = TypeVar("U")


def setup_logging(level: str = "INFO") -> logging.Logger:
    """Set up and return a configured logger"""
    levels = {
        "DEBUG": logging.DEBUG,
        "INFO": logging.INFO,
        "WARNING": logging.WARNING,
        "ERROR": logging.ERROR,
        "CRITICAL": logging.CRITICAL,
    }

    logger = logging.getLogger("test_repo")
    logger.setLevel(levels.get(level.upper(), logging.INFO))

    handler = logging.StreamHandler()
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    return logger


# Decorator example
def log_execution(func: Callable) -> Callable:
    """Decorator to log function execution"""

    def wrapper(*args, **kwargs):
        logger = logging.getLogger("test_repo")
        logger.info(f"Executing function: {func.__name__}")
        result = func(*args, **kwargs)
        logger.info(f"Completed function: {func.__name__}")
        return result

    return wrapper


# Higher-order function
def map_list(items: list[T], mapper: Callable[[T], U]) -> list[U]:
    """Map a function over a list of items"""
    return [mapper(item) for item in items]


# Class with various Python features
class ConfigManager:
    """Manages configuration with various access patterns"""

    _instance = None

    # Singleton pattern
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, initial_config: dict[str, Any] | None = None):
        if not hasattr(self, "initialized"):
            self.config = initial_config or {}
            self.initialized = True

    def __getitem__(self, key: str) -> Any:
        """Allow dictionary-like access"""
        return self.config.get(key)

    def __setitem__(self, key: str, value: Any) -> None:
        """Allow dictionary-like setting"""
        self.config[key] = value

    @property
    def debug_mode(self) -> bool:
        """Property example"""
        return self.config.get("debug", False)

    @debug_mode.setter
    def debug_mode(self, value: bool) -> None:
        self.config["debug"] = value


# Context manager example
class Timer:
    """Context manager for timing code execution"""

    def __init__(self, name: str = "Timer"):
        self.name = name
        self.start_time = None
        self.end_time = None

    def __enter__(self):
        import time

        self.start_time = time.time()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        import time

        self.end_time = time.time()
        print(f"{self.name} took {self.end_time - self.start_time:.6f} seconds")


# Functions with default arguments
def retry(func: Callable, max_attempts: int = 3, delay: float = 1.0) -> Any:
    """Retry a function with backoff"""
    import time

    for attempt in range(max_attempts):
        try:
            return func()
        except Exception as e:
            if attempt == max_attempts - 1:
                raise e
            time.sleep(delay * (2**attempt))

```

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

```typescript
import { ref, computed } from 'vue'
import type { Ref, ComputedRef } from 'vue'
import type { FormatOptions } from '@/types'

/**
 * Composable for formatting numbers with various options.
 * Demonstrates: composable pattern, refs, computed, type imports
 */
export function useFormatter(initialPrecision: number = 2) {
  // State
  const precision = ref<number>(initialPrecision)
  const useGrouping = ref<boolean>(true)
  const locale = ref<string>('en-US')

  // Computed properties
  const formatOptions = computed((): FormatOptions => ({
    maxDecimals: precision.value,
    useGrouping: useGrouping.value
  }))

  // Methods
  const formatNumber = (value: number): string => {
    return value.toLocaleString(locale.value, {
      minimumFractionDigits: precision.value,
      maximumFractionDigits: precision.value,
      useGrouping: useGrouping.value
    })
  }

  const formatCurrency = (value: number, currency: string = 'USD'): string => {
    return value.toLocaleString(locale.value, {
      style: 'currency',
      currency,
      minimumFractionDigits: precision.value,
      maximumFractionDigits: precision.value
    })
  }

  const formatPercentage = (value: number): string => {
    return `${(value * 100).toFixed(precision.value)}%`
  }

  const setPrecision = (newPrecision: number): void => {
    if (newPrecision >= 0 && newPrecision <= 10) {
      precision.value = newPrecision
    }
  }

  const toggleGrouping = (): void => {
    useGrouping.value = !useGrouping.value
  }

  const setLocale = (newLocale: string): void => {
    locale.value = newLocale
  }

  // Return composable API
  return {
    // State (readonly)
    precision: computed(() => precision.value),
    useGrouping: computed(() => useGrouping.value),
    locale: computed(() => locale.value),
    formatOptions,

    // Methods
    formatNumber,
    formatCurrency,
    formatPercentage,
    setPrecision,
    toggleGrouping,
    setLocale
  }
}

/**
 * Composable for time formatting.
 * Demonstrates: simpler composable, pure functions
 */
export function useTimeFormatter() {
  const formatTime = (date: Date): string => {
    return date.toLocaleTimeString('en-US', {
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit'
    })
  }

  const formatDate = (date: Date): string => {
    return date.toLocaleDateString('en-US', {
      year: 'numeric',
      month: 'long',
      day: 'numeric'
    })
  }

  const formatDateTime = (date: Date): string => {
    return `${formatDate(date)} ${formatTime(date)}`
  }

  const getRelativeTime = (date: Date): string => {
    const now = new Date()
    const diffMs = now.getTime() - date.getTime()
    const diffSecs = Math.floor(diffMs / 1000)
    const diffMins = Math.floor(diffSecs / 60)
    const diffHours = Math.floor(diffMins / 60)
    const diffDays = Math.floor(diffHours / 24)

    if (diffSecs < 60) return 'just now'
    if (diffMins < 60) return `${diffMins} minute${diffMins > 1 ? 's' : ''} ago`
    if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`
    return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`
  }

  return {
    formatTime,
    formatDate,
    formatDateTime,
    getRelativeTime
  }
}

/**
 * Type definitions for return types
 */
export type UseFormatterReturn = ReturnType<typeof useFormatter>
export type UseTimeFormatterReturn = ReturnType<typeof useTimeFormatter>

```

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

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

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

import pytest

from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language


@pytest.mark.markdown
class TestMarkdownLanguageServerBasics:
    """Test basic functionality of the markdown language server."""

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

    @pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
    def test_markdown_request_document_symbols(self, language_server: SolidLanguageServer) -> None:
        """Test request_document_symbols for markdown files."""
        # Test getting symbols from README.md
        all_symbols, _root_symbols = language_server.request_document_symbols("README.md").get_all_symbols_and_roots()

        # Extract heading symbols (LSP Symbol Kind 15 is String, but marksman uses kind 15 for headings)
        # Note: Different markdown LSPs may use different symbol kinds for headings
        # Marksman typically uses kind 15 (String) for markdown headings
        heading_names = [symbol["name"] for symbol in all_symbols]

        # Should detect headings from README.md
        assert "Test Repository" in heading_names or len(all_symbols) > 0, "Should find at least one heading"

    @pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
    def test_markdown_request_symbols_from_guide(self, language_server: SolidLanguageServer) -> None:
        """Test symbol detection in guide.md file."""
        all_symbols, _root_symbols = language_server.request_document_symbols("guide.md").get_all_symbols_and_roots()

        # At least some headings should be found
        assert len(all_symbols) > 0, f"Should find headings in guide.md, found {len(all_symbols)}"

    @pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
    def test_markdown_request_symbols_from_api(self, language_server: SolidLanguageServer) -> None:
        """Test symbol detection in api.md file."""
        all_symbols, _root_symbols = language_server.request_document_symbols("api.md").get_all_symbols_and_roots()

        # Should detect headings from api.md
        assert len(all_symbols) > 0, f"Should find headings in api.md, found {len(all_symbols)}"

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

        # Should have found some symbols
        assert len(all_symbols) > 0, "Should find symbols in README.md"

        # Note: Not all markdown LSPs provide body information for symbols
        # This test is more lenient and just verifies the API works
        assert all_symbols is not None, "Should return symbols even if body extraction is limited"

```

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

```python
from typing import Literal

from serena.tools import ReplaceContentTool, Tool, ToolMarkerCanEdit


class WriteMemoryTool(Tool, ToolMarkerCanEdit):
    """
    Writes a named memory (for future reference) to Serena's project-specific memory store.
    """

    def apply(self, memory_file_name: str, content: str, max_answer_chars: int = -1) -> str:
        """
        Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format.
        The memory name should be meaningful.
        """
        # NOTE: utf-8 encoding is configured in the MemoriesManager
        if max_answer_chars == -1:
            max_answer_chars = self.agent.serena_config.default_max_tool_answer_chars
        if len(content) > max_answer_chars:
            raise ValueError(
                f"Content for {memory_file_name} is too long. Max length is {max_answer_chars} characters. "
                + "Please make the content shorter."
            )

        return self.memories_manager.save_memory(memory_file_name, content)


class ReadMemoryTool(Tool):
    """
    Reads the memory with the given name from Serena's project-specific memory store.
    """

    def apply(self, memory_file_name: str, max_answer_chars: int = -1) -> str:
        """
        Read the content of a memory file. This tool should only be used if the information
        is relevant to the current task. You can infer whether the information
        is relevant from the memory file name.
        You should not read the same memory file multiple times in the same conversation.
        """
        return self.memories_manager.load_memory(memory_file_name)


class ListMemoriesTool(Tool):
    """
    Lists memories in Serena's project-specific memory store.
    """

    def apply(self) -> str:
        """
        List available memories. Any memory can be read using the `read_memory` tool.
        """
        return self._to_json(self.memories_manager.list_memories())


class DeleteMemoryTool(Tool, ToolMarkerCanEdit):
    """
    Deletes a memory from Serena's project-specific memory store.
    """

    def apply(self, memory_file_name: str) -> str:
        """
        Delete a memory file. Should only happen if a user asks for it explicitly,
        for example by saying that the information retrieved from a memory file is no longer correct
        or no longer relevant for the project.
        """
        return self.memories_manager.delete_memory(memory_file_name)


class EditMemoryTool(Tool, ToolMarkerCanEdit):
    def apply(
        self,
        memory_file_name: str,
        needle: str,
        repl: str,
        mode: Literal["literal", "regex"],
    ) -> str:
        r"""
        Replaces content matching a regular expression in a memory.

        :param memory_file_name: the name of the memory
        :param needle: the string or regex pattern to search for.
            If `mode` is "literal", this string will be matched exactly.
            If `mode` is "regex", this string will be treated as a regular expression (syntax of Python's `re` module,
            with flags DOTALL and MULTILINE enabled).
        :param repl: the replacement string (verbatim).
        :param mode: either "literal" or "regex", specifying how the `needle` parameter is to be interpreted.
        """
        replace_content_tool = self.agent.get_tool(ReplaceContentTool)
        rel_path = self.memories_manager.get_memory_file_path(memory_file_name).relative_to(self.get_project_root())
        return replace_content_tool.replace_content(str(rel_path), needle, repl, mode=mode, require_not_ignored=False)

```

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

```python
import time

import pytest

from serena.task_executor import TaskExecutor


@pytest.fixture
def executor():
    """
    Fixture for a basic SerenaAgent without a project
    """
    return TaskExecutor("TestExecutor")


class Task:
    def __init__(self, delay: float, exception: bool = False):
        self.delay = delay
        self.exception = exception
        self.did_run = False

    def run(self):
        self.did_run = True
        time.sleep(self.delay)
        if self.exception:
            raise ValueError("Task failed")
        return True


def test_task_executor_sequence(executor):
    """
    Tests that a sequence of tasks is executed correctly
    """
    future1 = executor.issue_task(Task(1).run, name="task1")
    future2 = executor.issue_task(Task(1).run, name="task2")
    assert future1.result() is True
    assert future2.result() is True


def test_task_executor_exception(executor):
    """
    Tests that tasks that raise exceptions are handled correctly, i.e. that
      * the exception is propagated,
      * subsequent tasks are still executed.
    """
    future1 = executor.issue_task(Task(1, exception=True).run, name="task1")
    future2 = executor.issue_task(Task(1).run, name="task2")
    have_exception = False
    try:
        assert future1.result()
    except Exception as e:
        assert isinstance(e, ValueError)
        have_exception = True
    assert have_exception
    assert future2.result() is True


def test_task_executor_cancel_current(executor):
    """
    Tests that tasks that are cancelled are handled correctly, i.e. that
      * subsequent tasks are executed as soon as cancellation ensues.
      * the cancelled task raises CancelledError when result() is called.
    """
    start_time = time.time()
    future1 = executor.issue_task(Task(10).run, name="task1")
    future2 = executor.issue_task(Task(1).run, name="task2")
    time.sleep(1)
    future1.cancel()
    assert future2.result() is True
    end_time = time.time()
    assert (end_time - start_time) < 9, "Cancelled task did not stop in time"
    have_cancelled_error = False
    try:
        future1.result()
    except Exception as e:
        assert e.__class__.__name__ == "CancelledError"
        have_cancelled_error = True
    assert have_cancelled_error


def test_task_executor_cancel_future(executor):
    """
    Tests that when a future task is cancelled, it is never run at all
    """
    task1 = Task(10)
    task2 = Task(1)
    future1 = executor.issue_task(task1.run, name="task1")
    future2 = executor.issue_task(task2.run, name="task2")
    time.sleep(1)
    future2.cancel()
    future1.cancel()
    try:
        future2.result()
    except:
        pass
    assert task1.did_run
    assert not task2.did_run


def test_task_executor_cancellation_via_task_info(executor):
    start_time = time.time()
    executor.issue_task(Task(10).run, "task1")
    executor.issue_task(Task(10).run, "task2")
    task_infos = executor.get_current_tasks()
    task_infos2 = executor.get_current_tasks()

    # test expected tasks
    assert len(task_infos) == 2
    assert "task1" in task_infos[0].name
    assert "task2" in task_infos[1].name

    # test task identifiers being stable
    assert task_infos2[0].task_id == task_infos[0].task_id

    # test cancellation
    task_infos[0].cancel()
    time.sleep(0.5)
    task_infos3 = executor.get_current_tasks()
    assert len(task_infos3) == 1  # Cancelled task is gone from the queue
    task_infos3[0].cancel()
    try:
        task_infos3[0].future.result()
    except:
        pass
    end_time = time.time()
    assert (end_time - start_time) < 9, "Cancelled task did not stop in time"

```

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

```python
import sys
import zipfile
from pathlib import Path

import pytest

from solidlsp.util.zip import SafeZipExtractor


@pytest.fixture
def temp_zip_file(tmp_path: Path) -> Path:
    """Create a temporary ZIP file for testing."""
    zip_path = tmp_path / "test.zip"
    with zipfile.ZipFile(zip_path, "w") as zipf:
        zipf.writestr("file1.txt", "Hello World 1")
        zipf.writestr("file2.txt", "Hello World 2")
        zipf.writestr("folder/file3.txt", "Hello World 3")
    return zip_path


def test_extract_all_success(temp_zip_file: Path, tmp_path: Path) -> None:
    """All files should extract without error."""
    dest_dir = tmp_path / "extracted"
    extractor = SafeZipExtractor(temp_zip_file, dest_dir, verbose=False)
    extractor.extract_all()

    assert (dest_dir / "file1.txt").read_text() == "Hello World 1"
    assert (dest_dir / "file2.txt").read_text() == "Hello World 2"
    assert (dest_dir / "folder" / "file3.txt").read_text() == "Hello World 3"


def test_include_patterns(temp_zip_file: Path, tmp_path: Path) -> None:
    """Only files matching include_patterns should be extracted."""
    dest_dir = tmp_path / "extracted"
    extractor = SafeZipExtractor(temp_zip_file, dest_dir, verbose=False, include_patterns=["*.txt"])
    extractor.extract_all()

    assert (dest_dir / "file1.txt").exists()
    assert (dest_dir / "file2.txt").exists()
    assert (dest_dir / "folder" / "file3.txt").exists()


def test_exclude_patterns(temp_zip_file: Path, tmp_path: Path) -> None:
    """Files matching exclude_patterns should be skipped."""
    dest_dir = tmp_path / "extracted"
    extractor = SafeZipExtractor(temp_zip_file, dest_dir, verbose=False, exclude_patterns=["file2.txt"])
    extractor.extract_all()

    assert (dest_dir / "file1.txt").exists()
    assert not (dest_dir / "file2.txt").exists()
    assert (dest_dir / "folder" / "file3.txt").exists()


def test_include_and_exclude_patterns(temp_zip_file: Path, tmp_path: Path) -> None:
    """Exclude should override include if both match."""
    dest_dir = tmp_path / "extracted"
    extractor = SafeZipExtractor(
        temp_zip_file,
        dest_dir,
        verbose=False,
        include_patterns=["*.txt"],
        exclude_patterns=["file1.txt"],
    )
    extractor.extract_all()

    assert not (dest_dir / "file1.txt").exists()
    assert (dest_dir / "file2.txt").exists()
    assert (dest_dir / "folder" / "file3.txt").exists()


def test_skip_on_error(monkeypatch, temp_zip_file: Path, tmp_path: Path) -> None:
    """Should skip a file that raises an error and continue extracting others."""
    dest_dir = tmp_path / "extracted"

    original_open = zipfile.ZipFile.open

    def failing_open(self, member, *args, **kwargs):
        if member.filename == "file2.txt":
            raise OSError("Simulated failure")
        return original_open(self, member, *args, **kwargs)

    # Patch the method on the class, not on an instance
    monkeypatch.setattr(zipfile.ZipFile, "open", failing_open)

    extractor = SafeZipExtractor(temp_zip_file, dest_dir, verbose=False)
    extractor.extract_all()

    assert (dest_dir / "file1.txt").exists()
    assert not (dest_dir / "file2.txt").exists()
    assert (dest_dir / "folder" / "file3.txt").exists()


@pytest.mark.skipif(not sys.platform.startswith("win"), reason="Windows-only test")
def test_long_path_normalization(temp_zip_file: Path, tmp_path: Path) -> None:
    r"""Ensure _normalize_path adds \\?\\ prefix on Windows."""
    dest_dir = tmp_path / ("a" * 250)  # Simulate long path
    extractor = SafeZipExtractor(temp_zip_file, dest_dir, verbose=False)
    norm_path = extractor._normalize_path(dest_dir / "file.txt")
    assert str(norm_path).startswith("\\\\?\\")

```

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

```python
import queue
import threading
from collections.abc import Callable
from typing import Optional

from sensai.util import logging

from serena.constants import SERENA_LOG_FORMAT

lg = logging


class MemoryLogHandler(logging.Handler):
    def __init__(self, level: int = logging.NOTSET) -> None:
        super().__init__(level=level)
        self.setFormatter(logging.Formatter(SERENA_LOG_FORMAT))
        self._log_buffer = LogBuffer()
        self._log_queue: queue.Queue[str] = queue.Queue()
        self._stop_event = threading.Event()
        self._emit_callbacks: list[Callable[[str], None]] = []

        # start background thread to process logs
        self.worker_thread = threading.Thread(target=self._process_queue, daemon=True)
        self.worker_thread.start()

    def add_emit_callback(self, callback: Callable[[str], None]) -> None:
        """
        Adds a callback that will be called with each log message.
        The callback should accept a single string argument (the log message).
        """
        self._emit_callbacks.append(callback)

    def emit(self, record: logging.LogRecord) -> None:
        msg = self.format(record)
        self._log_queue.put_nowait(msg)

    def _process_queue(self) -> None:
        while not self._stop_event.is_set():
            try:
                msg = self._log_queue.get(timeout=1)
                self._log_buffer.append(msg)
                for callback in self._emit_callbacks:
                    try:
                        callback(msg)
                    except:
                        pass
                self._log_queue.task_done()
            except queue.Empty:
                continue

    def get_log_messages(self) -> list[str]:
        return self._log_buffer.get_log_messages()


class LogBuffer:
    """
    A thread-safe buffer for storing log messages.
    """

    def __init__(self) -> None:
        self._log_messages: list[str] = []
        self._lock = threading.Lock()

    def append(self, msg: str) -> None:
        with self._lock:
            self._log_messages.append(msg)

    def get_log_messages(self) -> list[str]:
        with self._lock:
            return self._log_messages.copy()


class SuspendedLoggersContext:
    """A context manager that provides an isolated logging environment.

    Temporarily removes all root log handlers upon entry, providing a clean slate
    for defining new log handlers within the context. Upon exit, restores the original
    logging configuration. This is useful when you need to temporarily configure
    an isolated logging setup with well-defined log handlers.

    The context manager:
        - Removes all existing (root) log handlers on entry
        - Allows defining new temporary handlers within the context
        - Restores the original configuration (handlers and root log level) on exit

    Example:
        >>> with SuspendedLoggersContext():
        ...     # No handlers are active here (configure your own and set desired log level)
        ...     pass
        >>> # Original log handlers are restored here

    """

    def __init__(self) -> None:
        self.saved_root_handlers: list = []
        self.saved_root_level: Optional[int] = None

    def __enter__(self) -> "SuspendedLoggersContext":
        root_logger = lg.getLogger()
        self.saved_root_handlers = root_logger.handlers.copy()
        self.saved_root_level = root_logger.level
        root_logger.handlers.clear()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:  # type: ignore
        root_logger = lg.getLogger()
        root_logger.handlers = self.saved_root_handlers
        if self.saved_root_level is not None:
            root_logger.setLevel(self.saved_root_level)

```

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

```markdown
# Roadmap

This document gives an overview of the ongoing and future development of Serena.
If you have a proposal or want to discuss something, feel free to open a discussion
on Github. For a summary of the past development, see the [changelog](/CHANGELOG.md).

Want to see us reach our goals faster? You can help out with an issue, start a discussion, or 
inform us about funding opportunities so that we can devote more time to the project.

## Overall Goals

Serena has the potential to be the go-to tool for most LLM coding tasks, since it is 
unique in its ability to be used as MCP Server in any kind of environment
while still being a capable agent. We want to achieve the following goals in terms of functionality:

1. Top performance (comparable to API-based coding agents) when used through official (free) clients like Claude Desktop.
1. Lowering API costs and potentially improving performance of coding clients (Claude Code, Codex, Cline, Roo, Cursor/Windsurf/VSCode etc).
1. Transparency and simplicity of use. Achieved through the dashboard/logging GUI.
1. Integrations with major frameworks that don't accept MCP. Usable as a library.

Apart from the functional goals, we have the goal of having great code design, so that Serena can be viewed
as a reference for how to implement MCP Servers. Such projects are an emerging technology, and
best practices are yet to be determined. We will share our experiences in [lessons learned](/lessons_learned.md).


## Immediate/Ongoing

- Support for projects using multiple programming languages.
- Evaluate whether `ReplaceLinesTool` can be removed in favor of a more reliable and performant editing approach.
- Generally experiment with various approaches to editing tools
- Manual evaluation on selected tasks from SWE-verified
- Manual evaluation of cost-lowering and performance when used within popular non-MCP agents
- Improvements in prompts, in particular giving examples and extending modes and contexts

## Upcoming

- Publishing Serena as a package that can also be used as library
- Use linting and type-hierarchy from the LSP in tools
- Tools for refactoring (rename, move) - speculative, maybe won't do this.
- Tracking edits and rolling them back with the dashboard
- Improve configurability and safety of shell tool. Maybe autogeneration of tools from a list of commands and descriptions.
- Transparent comparison with DesktopCommander and ...
- Automatic evaluation using OpenHands, submission to SWE-Bench
- Evaluation whether incorporating other MCPs increases performance or usability (memory bank is a candidate)
- More documentation and best practices

## Stretch

- Allow for sandboxing and parallel instances of Serena, maybe use openhands or codex for that
- Incorporate a verifier model or generally a second model (maybe for applying edits) as a tool.
- Building on the above, allow for the second model itself to be reachable through an MCP server, so it can be used for free
- Tracking edits performed with shell tools

## Beyond Serena

The technologies and approaches taken in Serena can be used for various research and service ideas. Some thought that we had are:

- PR and issue assistant working with GitHub, similar to how [OpenHands](https://github.com/All-Hands-AI/OpenHands) 
  and [qodo](https://github.com/qodo-ai/pr-agent) operate. Should be callable through @serena
- Tuning a coding LLM with Serena's tools with RL on one-shot tasks. We would need compute-funding for that
- Develop a web app to quantitatively compare the performance of various agents by scraping PRs and manually crafted metadata.
  The main metric for coding agents should be *developer experience*, and that is hard to grasp and is poorly correlated with
  performance on current benchmarks.
```

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

```markdown
# List of Tools

Find the full list of Serena's tools below (output of `<serena> tools list --all`).

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).

* `activate_project`: Activates a project based on the project name or path.
* `check_onboarding_performed`: Checks whether project onboarding was already performed.
* `create_text_file`: Creates/overwrites a file in the project directory.
* `delete_lines`: Deletes a range of lines within a file.
* `delete_memory`: Deletes a memory from Serena's project-specific memory store.
* `execute_shell_command`: Executes a shell command.
* `find_file`: Finds files in the given relative paths
* `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
* `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filter
  ed by type).
* `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools,
  contexts, and modes.
* `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
* `initial_instructions`: Provides instructions on how to use the Serena toolbox.
* `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
* `insert_at_line`: Inserts content at a given line in a file.
* `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
* `jet_brains_find_referencing_symbols`: Finds symbols that reference the given symbol
* `jet_brains_find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (option
  ally filtered by type).
* `jet_brains_get_symbols_overview`: Retrieves an overview of the top-level symbols within a specified file
* `list_dir`: Lists files and directories in the given directory (optionally with recursion).
* `list_memories`: Lists memories in Serena's project-specific memory store.
* `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
* `open_dashboard`: Opens the Serena web dashboard in the default web browser.
* `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with th
  e necessary context).
* `read_file`: Reads a file within the project directory.
* `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
* `remove_project`: Removes a project from the Serena configuration.
* `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities.
* `replace_lines`: Replaces a range of lines within a file with new content.
* `replace_content`: Replaces content in a file (optionally using regular expressions).
* `replace_symbol_body`: Replaces the full definition of a symbol.
* `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
* `search_for_pattern`: Performs a search for a pattern in the project.
* `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
* `switch_modes`: Activates modes by providing a list of their names
* `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
* `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
* `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
* `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.

```

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

```python
import logging
import os
from typing import Any

from .multilang_prompt import DEFAULT_LANG_CODE, LanguageFallbackMode, MultiLangPromptCollection, PromptList

log = logging.getLogger(__name__)


class PromptFactoryBase:
    """Base class for auto-generated prompt factory classes."""

    def __init__(self, prompts_dir: str | list[str], lang_code: str = DEFAULT_LANG_CODE, fallback_mode=LanguageFallbackMode.EXCEPTION):
        """
        :param prompts_dir: the directory containing the prompt templates and prompt lists.
            If a list is provided, will look for prompt templates in the dirs from left to right
            (first one containing the desired template wins).
        :param lang_code: the language code to use for retrieving the prompt templates and prompt lists.
            Leave as `default` for single-language use cases.
        :param fallback_mode: the fallback mode to use when a prompt template or prompt list is not found for the requested language.
            Irrelevant for single-language use cases.
        """
        self.lang_code = lang_code
        self._prompt_collection = MultiLangPromptCollection(prompts_dir, fallback_mode=fallback_mode)

    def _render_prompt(self, prompt_name: str, params: dict[str, Any]) -> str:
        del params["self"]
        return self._prompt_collection.render_prompt_template(prompt_name, params, lang_code=self.lang_code)

    def _get_prompt_list(self, prompt_name: str) -> PromptList:
        return self._prompt_collection.get_prompt_list(prompt_name, self.lang_code)


def autogenerate_prompt_factory_module(prompts_dir: str, target_module_path: str) -> None:
    """
    Auto-generates a prompt factory module for the given prompt directory.
    The generated `PromptFactory` class is meant to be the central entry class for retrieving and rendering prompt templates and prompt
    lists in your application.
    It will contain one method per prompt template and prompt list, and is useful for both single- and multi-language use cases.

    :param prompts_dir: the directory containing the prompt templates and prompt lists
    :param target_module_path: the path to the target module file (.py). Important: The module will be overwritten!
    """
    generated_code = """
# ruff: noqa
# black: skip
# mypy: ignore-errors

# NOTE: This module is auto-generated from interprompt.autogenerate_prompt_factory_module, do not edit manually!

from interprompt.multilang_prompt import PromptList
from interprompt.prompt_factory import PromptFactoryBase
from typing import Any


class PromptFactory(PromptFactoryBase):
    \"""
    A class for retrieving and rendering prompt templates and prompt lists.
    \"""
"""
    # ---- add methods based on prompt template names and parameters and prompt list names ----
    prompt_collection = MultiLangPromptCollection(prompts_dir)

    for template_name in prompt_collection.get_prompt_template_names():
        template_parameters = prompt_collection.get_prompt_template_parameters(template_name)
        if len(template_parameters) == 0:
            method_params_str = ""
        else:
            method_params_str = ", *, " + ", ".join([f"{param}: Any" for param in template_parameters])
        generated_code += f"""
    def create_{template_name}(self{method_params_str}) -> str:
        return self._render_prompt('{template_name}', locals())
"""
    for prompt_list_name in prompt_collection.get_prompt_list_names():
        generated_code += f"""
    def get_list_{prompt_list_name}(self) -> PromptList:
        return self._get_prompt_list('{prompt_list_name}')
"""
    os.makedirs(os.path.dirname(target_module_path), exist_ok=True)
    with open(target_module_path, "w", encoding="utf-8") as f:
        f.write(generated_code)
    log.info(f"Prompt factory generated successfully in {target_module_path}")

```

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

```yaml
# The system prompt template. Note that many clients will not allow configuration of the actual system prompt,
# in which case this prompt will be given as a regular message on the call of a simple tool which the agent
# is encouraged (via the tool description) to call at the beginning of the conversation.
prompts:
  system_prompt: |
    You are a professional coding agent. 
    You have access to semantic coding tools upon which you rely heavily for all your work.
    You operate in a resource-efficient and intelligent manner, always keeping in mind to not read or generate
    content that is not needed for the task at hand.

    Some tasks may require you to understand the architecture of large parts of the codebase, while for others,
    it may be enough to read a small set of symbols or a single file.
    You avoid reading entire files unless it is absolutely necessary, instead relying on intelligent step-by-step 
    acquisition of information. {% if 'ToolMarkerSymbolicRead' in available_markers %}Once you have read a full file, it does not make
    sense to analyse it with the symbolic read tools; you already have the information.{% endif %}

    You can achieve intelligent reading of code by using the symbolic tools for getting an overview of symbols and
    the relations between them, and then only reading the bodies of symbols that are necessary to complete the task at hand. 
    You can use the standard tools like list_dir, find_file and search_for_pattern if you need to.
    Where appropriate, you pass the `relative_path` parameter to restrict the search to a specific file or directory.
    {% if 'search_for_pattern' in available_tools %}
    If you are unsure about a symbol's name or location{% if 'find_symbol' in available_tools %} (to the extent that substring_matching for the symbol name is not enough){% endif %}, you can use the `search_for_pattern` tool, which allows fast
    and flexible search for patterns in the codebase.{% if 'ToolMarkerSymbolicRead' in available_markers %} In this way, you can first find candidates for symbols or files,
    and then proceed with the symbolic tools.{% endif %}
    {% endif %}

    {% if 'ToolMarkerSymbolicRead' in available_markers %}
    Symbols are identified by their `name_path` and `relative_path` (see the description of the `find_symbol` tool).
    You can get information about the symbols in a file by using the `get_symbols_overview` tool or use the `find_symbol` to search. 
    You only read the bodies of symbols when you need to (e.g. if you want to fully understand or edit it).
    For example, if you are working with Python code and already know that you need to read the body of the constructor of the class Foo, you can directly
    use `find_symbol` with name path pattern `Foo/__init__` and `include_body=True`. If you don't know yet which methods in `Foo` you need to read or edit,
    you can use `find_symbol` with name path pattern `Foo`, `include_body=False` and `depth=1` to get all (top-level) methods of `Foo` before proceeding
    to read the desired methods with `include_body=True`.
    You can understand relationships between symbols by using the `find_referencing_symbols` tool.
    {% endif %}

    {% if 'read_memory' in available_tools %}
    You generally have access to memories and it may be useful for you to read them.
    You infer whether memories are relevant based on their names.
    {% endif %}

    The context and modes of operation are described below. These determine how to interact with your user
    and which kinds of interactions are expected of you.

    Context description:
    {{ context_system_prompt }}

    Modes descriptions:
    {% for prompt in mode_system_prompts %}
    {{ prompt }}
    {% endfor %}
    
    You have hereby read the 'Serena Instructions Manual' and do not need to read it again.

```

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

```typescript
import { defineStore } from 'pinia'
import type { HistoryEntry, Operation, CalculatorState } from '@/types'

export const useCalculatorStore = defineStore('calculator', {
  state: (): CalculatorState => ({
    currentValue: 0,
    previousValue: null,
    operation: null,
    history: [],
    displayValue: '0'
  }),

  getters: {
    /**
     * Get the most recent history entries (last 10)
     */
    recentHistory: (state): HistoryEntry[] => {
      return state.history.slice(-10).reverse()
    },

    /**
     * Check if calculator has any history
     */
    hasHistory: (state): boolean => {
      return state.history.length > 0
    },

    /**
     * Get the current display text
     */
    display: (state): string => {
      return state.displayValue
    }
  },

  actions: {
    /**
     * Set a number value
     */
    setNumber(value: number) {
      this.currentValue = value
      this.displayValue = value.toString()
    },

    /**
     * Append a digit to the current value
     */
    appendDigit(digit: number) {
      if (this.displayValue === '0') {
        this.displayValue = digit.toString()
      } else {
        this.displayValue += digit.toString()
      }
      this.currentValue = parseFloat(this.displayValue)
    },

    /**
     * Add two numbers
     */
    add() {
      if (this.previousValue !== null && this.operation) {
        this.executeOperation()
      }
      this.previousValue = this.currentValue
      this.operation = 'add'
      this.displayValue = '0'
    },

    /**
     * Subtract two numbers
     */
    subtract() {
      if (this.previousValue !== null && this.operation) {
        this.executeOperation()
      }
      this.previousValue = this.currentValue
      this.operation = 'subtract'
      this.displayValue = '0'
    },

    /**
     * Multiply two numbers
     */
    multiply() {
      if (this.previousValue !== null && this.operation) {
        this.executeOperation()
      }
      this.previousValue = this.currentValue
      this.operation = 'multiply'
      this.displayValue = '0'
    },

    /**
     * Divide two numbers
     */
    divide() {
      if (this.previousValue !== null && this.operation) {
        this.executeOperation()
      }
      this.previousValue = this.currentValue
      this.operation = 'divide'
      this.displayValue = '0'
    },

    /**
     * Execute the pending operation
     */
    executeOperation() {
      if (this.previousValue === null || this.operation === null) {
        return
      }

      let result = 0
      const prev = this.previousValue
      const current = this.currentValue
      let expression = ''

      switch (this.operation) {
        case 'add':
          result = prev + current
          expression = `${prev} + ${current}`
          break
        case 'subtract':
          result = prev - current
          expression = `${prev} - ${current}`
          break
        case 'multiply':
          result = prev * current
          expression = `${prev} × ${current}`
          break
        case 'divide':
          if (current === 0) {
            this.displayValue = 'Error'
            this.clear()
            return
          }
          result = prev / current
          expression = `${prev} ÷ ${current}`
          break
      }

      // Add to history
      this.history.push({
        expression,
        result,
        timestamp: new Date()
      })

      this.currentValue = result
      this.displayValue = result.toString()
      this.previousValue = null
      this.operation = null
    },

    /**
     * Calculate the equals operation
     */
    equals() {
      this.executeOperation()
    },

    /**
     * Clear the calculator state
     */
    clear() {
      this.currentValue = 0
      this.previousValue = null
      this.operation = null
      this.displayValue = '0'
    },

    /**
     * Clear all history
     */
    clearHistory() {
      this.history = []
    }
  }
})

```

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

```markdown
# Serena Repository Structure

## Overview
Serena is a multi-language code assistant that combines two main components:
1. **Serena Core** - The main agent framework with tools and MCP server
2. **SolidLSP** - A unified Language Server Protocol wrapper for multiple programming languages

## Top-Level Structure

```
serena/
├── src/                          # Main source code
│   ├── serena/                   # Serena agent framework
│   ├── solidlsp/                 # LSP wrapper library  
│   └── interprompt/              # Multi-language prompt templates
├── test/                         # Test suites
│   ├── serena/                   # Serena agent tests
│   ├── solidlsp/                 # Language server tests
│   └── resources/repos/          # Test repositories for each language
├── scripts/                      # Build and utility scripts
├── resources/                    # Static resources and configurations
├── pyproject.toml               # Python project configuration
├── README.md                    # Project documentation
└── CHANGELOG.md                 # Version history
```

## Source Code Organization

### Serena Core (`src/serena/`)
- **`agent.py`** - Main SerenaAgent class that orchestrates everything
- **`tools/`** - MCP tools for file operations, symbols, memory, etc.
  - `file_tools.py` - File system operations (read, write, search)
  - `symbol_tools.py` - Symbol-based code operations (find, edit)
  - `memory_tools.py` - Knowledge persistence and retrieval
  - `config_tools.py` - Project and mode management
  - `workflow_tools.py` - Onboarding and meta-operations
- **`config/`** - Configuration management
  - `serena_config.py` - Main configuration classes
  - `context_mode.py` - Context and mode definitions
- **`util/`** - Utility modules
- **`mcp.py`** - MCP server implementation
- **`cli.py`** - Command-line interface

### SolidLSP (`src/solidlsp/`)
- **`ls.py`** - Main SolidLanguageServer class
- **`language_servers/`** - Language-specific implementations
  - `csharp_language_server.py` - C# (Microsoft.CodeAnalysis.LanguageServer)
  - `python_server.py` - Python (Pyright)
  - `typescript_language_server.py` - TypeScript
  - `rust_analyzer.py` - Rust
  - `gopls.py` - Go
  - And many more...
- **`ls_config.py`** - Language server configuration
- **`ls_types.py`** - LSP type definitions
- **`ls_utils.py`** - Utilities for working with LSP data

### Interprompt (`src/interprompt/`)
- Multi-language prompt template system
- Jinja2-based templating with language fallbacks

## Test Structure

### Language Server Tests (`test/solidlsp/`)
Each language has its own test directory:
```
test/solidlsp/
├── csharp/
│   └── test_csharp_basic.py
├── python/
│   └── test_python_basic.py
├── typescript/
│   └── test_typescript_basic.py
└── ...
```

### Test Resources (`test/resources/repos/`)
Contains minimal test projects for each language:
```
test/resources/repos/
├── csharp/test_repo/
│   ├── serena.sln
│   ├── TestProject.csproj
│   ├── Program.cs
│   └── Models/Person.cs
├── python/test_repo/
├── typescript/test_repo/
└── ...
```

### Test Infrastructure
- **`test/conftest.py`** - Shared test fixtures and utilities
- **`create_ls()`** function - Creates language server instances for testing
- **`language_server` fixture** - Parametrized fixture for multi-language tests

## Key Configuration Files

- **`pyproject.toml`** - Python dependencies, build config, and tool settings
- **`.serena/`** directories - Project-specific Serena configuration and memories
- **`CLAUDE.md`** - Instructions for AI assistants working on the project

## Dependencies Management

The project uses modern Python tooling:
- **uv** for fast dependency resolution and virtual environments
- **pytest** for testing with language-specific markers (`@pytest.mark.csharp`)
- **ruff** for linting and formatting
- **mypy** for type checking

## Build and Development

- **Docker support** - Full containerized development environment
- **GitHub Actions** - CI/CD with language server testing
- **Development scripts** in `scripts/` directory
```

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

```json
{
    "RoslynExtensionsOptions": {
        "EnableDecompilationSupport": false,
        "EnableAnalyzersSupport": true,
        "EnableImportCompletion": true,
        "EnableAsyncCompletion": false,
        "DocumentAnalysisTimeoutMs": 30000,
        "DiagnosticWorkersThreadCount": 18,
        "AnalyzeOpenDocumentsOnly": true,
        "InlayHintsOptions": {
            "EnableForParameters": false,
            "ForLiteralParameters": false,
            "ForIndexerParameters": false,
            "ForObjectCreationParameters": false,
            "ForOtherParameters": false,
            "SuppressForParametersThatDifferOnlyBySuffix": false,
            "SuppressForParametersThatMatchMethodIntent": false,
            "SuppressForParametersThatMatchArgumentName": false,
            "EnableForTypes": false,
            "ForImplicitVariableTypes": false,
            "ForLambdaParameterTypes": false,
            "ForImplicitObjectCreation": false
        },
        "LocationPaths": null
    },
    "FormattingOptions": {
        "OrganizeImports": false,
        "EnableEditorConfigSupport": true,
        "NewLine": "\n",
        "UseTabs": false,
        "TabSize": 4,
        "IndentationSize": 4,
        "SpacingAfterMethodDeclarationName": false,
        "SeparateImportDirectiveGroups": false,
        "SpaceWithinMethodDeclarationParenthesis": false,
        "SpaceBetweenEmptyMethodDeclarationParentheses": false,
        "SpaceAfterMethodCallName": false,
        "SpaceWithinMethodCallParentheses": false,
        "SpaceBetweenEmptyMethodCallParentheses": false,
        "SpaceAfterControlFlowStatementKeyword": true,
        "SpaceWithinExpressionParentheses": false,
        "SpaceWithinCastParentheses": false,
        "SpaceWithinOtherParentheses": false,
        "SpaceAfterCast": false,
        "SpaceBeforeOpenSquareBracket": false,
        "SpaceBetweenEmptySquareBrackets": false,
        "SpaceWithinSquareBrackets": false,
        "SpaceAfterColonInBaseTypeDeclaration": true,
        "SpaceAfterComma": true,
        "SpaceAfterDot": false,
        "SpaceAfterSemicolonsInForStatement": true,
        "SpaceBeforeColonInBaseTypeDeclaration": true,
        "SpaceBeforeComma": false,
        "SpaceBeforeDot": false,
        "SpaceBeforeSemicolonsInForStatement": false,
        "SpacingAroundBinaryOperator": "single",
        "IndentBraces": false,
        "IndentBlock": true,
        "IndentSwitchSection": true,
        "IndentSwitchCaseSection": true,
        "IndentSwitchCaseSectionWhenBlock": true,
        "LabelPositioning": "oneLess",
        "WrappingPreserveSingleLine": true,
        "WrappingKeepStatementsOnSingleLine": true,
        "NewLinesForBracesInTypes": true,
        "NewLinesForBracesInMethods": true,
        "NewLinesForBracesInProperties": true,
        "NewLinesForBracesInAccessors": true,
        "NewLinesForBracesInAnonymousMethods": true,
        "NewLinesForBracesInControlBlocks": true,
        "NewLinesForBracesInAnonymousTypes": true,
        "NewLinesForBracesInObjectCollectionArrayInitializers": true,
        "NewLinesForBracesInLambdaExpressionBody": true,
        "NewLineForElse": true,
        "NewLineForCatch": true,
        "NewLineForFinally": true,
        "NewLineForMembersInObjectInit": true,
        "NewLineForMembersInAnonymousTypes": true,
        "NewLineForClausesInQuery": true
    },
    "FileOptions": {
        "SystemExcludeSearchPatterns": [
            "**/node_modules/**/*",
            "**/bin/**/*",
            "**/obj/**/*",
            "**/.git/**/*",
            "**/.git",
            "**/.svn",
            "**/.hg",
            "**/CVS",
            "**/.DS_Store",
            "**/Thumbs.db"
        ],
        "ExcludeSearchPatterns": []
    },
    "RenameOptions": {
        "RenameOverloads": false,
        "RenameInStrings": false,
        "RenameInComments": false
    },
    "ImplementTypeOptions": {
        "InsertionBehavior": 0,
        "PropertyGenerationBehavior": 0
    },
    "DotNetCliOptions": {
        "LocationPaths": null
    },
    "Plugins": {
        "LocationPaths": null
    }
}
```

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

```
# Utility functions for PowerShell operations

<#
.SYNOPSIS
    Converts a string to uppercase.
.PARAMETER InputString
    The string to convert.
.OUTPUTS
    System.String - The uppercase string.
#>
function Convert-ToUpperCase {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]$InputString
    )

    return $InputString.ToUpper()
}

<#
.SYNOPSIS
    Converts a string to lowercase.
.PARAMETER InputString
    The string to convert.
.OUTPUTS
    System.String - The lowercase string.
#>
function Convert-ToLowerCase {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]$InputString
    )

    return $InputString.ToLower()
}

<#
.SYNOPSIS
    Removes leading and trailing whitespace from a string.
.PARAMETER InputString
    The string to trim.
.OUTPUTS
    System.String - The trimmed string.
#>
function Remove-Whitespace {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]$InputString
    )

    return $InputString.Trim()
}

<#
.SYNOPSIS
    Creates a backup of a file.
.PARAMETER FilePath
    The path to the file to backup.
.PARAMETER BackupDirectory
    The directory where the backup will be created.
.OUTPUTS
    System.String - The path to the backup file.
#>
function Backup-File {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [string]$FilePath,

        [Parameter(Mandatory = $false)]
        [string]$BackupDirectory = "."
    )

    if (-not (Test-Path $FilePath)) {
        throw "File not found: $FilePath"
    }

    $fileName = Split-Path $FilePath -Leaf
    $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
    $backupName = "$fileName.$timestamp.bak"
    $backupPath = Join-Path $BackupDirectory $backupName

    Copy-Item -Path $FilePath -Destination $backupPath
    return $backupPath
}

<#
.SYNOPSIS
    Checks if an array contains a specific element.
.PARAMETER Array
    The array to search.
.PARAMETER Element
    The element to find.
.OUTPUTS
    System.Boolean - True if the element is found, false otherwise.
#>
function Test-ArrayContains {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [array]$Array,

        [Parameter(Mandatory = $true)]
        $Element
    )

    return $Array -contains $Element
}

<#
.SYNOPSIS
    Writes a log message with timestamp.
.PARAMETER Message
    The message to log.
.PARAMETER Level
    The log level (Info, Warning, Error).
#>
function Write-LogMessage {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Message,

        [Parameter(Mandatory = $false)]
        [ValidateSet("Info", "Warning", "Error")]
        [string]$Level = "Info"
    )

    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $logEntry = "[$timestamp] [$Level] $Message"

    switch ($Level) {
        "Info" { Write-Host $logEntry -ForegroundColor White }
        "Warning" { Write-Host $logEntry -ForegroundColor Yellow }
        "Error" { Write-Host $logEntry -ForegroundColor Red }
    }
}

<#
.SYNOPSIS
    Validates if a string is a valid email address.
.PARAMETER Email
    The email address to validate.
.OUTPUTS
    System.Boolean - True if the email is valid, false otherwise.
#>
function Test-ValidEmail {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Email
    )

    $emailRegex = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
    return $Email -match $emailRegex
}

<#
.SYNOPSIS
    Checks if a string is a valid number.
.PARAMETER Value
    The string to check.
.OUTPUTS
    System.Boolean - True if the string is a valid number, false otherwise.
#>
function Test-IsNumber {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Value
    )

    $number = 0
    return [double]::TryParse($Value, [ref]$number)
}

```

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

```vue
<script setup lang="ts">
import { computed, ref } from 'vue'

/**
 * Props interface for CalculatorButton.
 * Demonstrates: defineProps with TypeScript interface
 */
interface Props {
  label: string | number
  variant?: 'digit' | 'operation' | 'equals' | 'clear'
  disabled?: boolean
  active?: boolean
  size?: 'small' | 'medium' | 'large'
}

/**
 * Emits interface for CalculatorButton.
 * Demonstrates: defineEmits with TypeScript
 */
interface Emits {
  click: [value: string | number]
  hover: [isHovering: boolean]
  focus: []
  blur: []
}

// Define props with defaults
const props = withDefaults(defineProps<Props>(), {
  variant: 'digit',
  disabled: false,
  active: false,
  size: 'medium'
})

// Define emits
const emit = defineEmits<Emits>()

// Local state
const isHovered = ref(false)
const isFocused = ref(false)
const pressCount = ref(0)

// Computed classes based on props and state
const buttonClass = computed(() => {
  const classes = ['calc-button', `calc-button--${props.variant}`, `calc-button--${props.size}`]

  if (props.active) classes.push('calc-button--active')
  if (props.disabled) classes.push('calc-button--disabled')
  if (isHovered.value) classes.push('calc-button--hovered')
  if (isFocused.value) classes.push('calc-button--focused')

  return classes.join(' ')
})

// Computed aria label for accessibility
const ariaLabel = computed(() => {
  const variantText = {
    digit: 'Number',
    operation: 'Operation',
    equals: 'Equals',
    clear: 'Clear'
  }[props.variant]

  return `${variantText}: ${props.label}`
})

// Event handlers that emit events
const handleClick = () => {
  if (!props.disabled) {
    pressCount.value++
    emit('click', props.label)
  }
}

const handleMouseEnter = () => {
  isHovered.value = true
  emit('hover', true)
}

const handleMouseLeave = () => {
  isHovered.value = false
  emit('hover', false)
}

const handleFocus = () => {
  isFocused.value = true
  emit('focus')
}

const handleBlur = () => {
  isFocused.value = false
  emit('blur')
}

// Expose internal state for parent access via template refs
// Demonstrates: defineExpose
defineExpose({
  pressCount,
  isHovered,
  isFocused,
  simulateClick: handleClick
})
</script>

<template>
  <button
    :class="buttonClass"
    :disabled="disabled"
    :aria-label="ariaLabel"
    @click="handleClick"
    @mouseenter="handleMouseEnter"
    @mouseleave="handleMouseLeave"
    @focus="handleFocus"
    @blur="handleBlur"
  >
    <span class="calc-button__label">{{ label }}</span>
    <span v-if="pressCount > 0" class="calc-button__badge">{{ pressCount }}</span>
  </button>
</template>

<style scoped>
.calc-button {
  position: relative;
  padding: 1rem;
  font-size: 1.2rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s;
  font-weight: 500;
}

.calc-button--small {
  padding: 0.5rem;
  font-size: 1rem;
}

.calc-button--medium {
  padding: 1rem;
  font-size: 1.2rem;
}

.calc-button--large {
  padding: 1.5rem;
  font-size: 1.5rem;
}

.calc-button--digit {
  background: white;
  color: #333;
}

.calc-button--digit:hover:not(:disabled) {
  background: #e0e0e0;
}

.calc-button--operation {
  background: #2196f3;
  color: white;
}

.calc-button--operation:hover:not(:disabled) {
  background: #1976d2;
}

.calc-button--operation.calc-button--active {
  background: #1565c0;
}

.calc-button--equals {
  background: #4caf50;
  color: white;
}

.calc-button--equals:hover:not(:disabled) {
  background: #45a049;
}

.calc-button--clear {
  background: #f44336;
  color: white;
}

.calc-button--clear:hover:not(:disabled) {
  background: #da190b;
}

.calc-button--disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.calc-button--hovered {
  transform: translateY(-2px);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}

.calc-button--focused {
  outline: 2px solid #2196f3;
  outline-offset: 2px;
}

.calc-button__label {
  display: block;
}

.calc-button__badge {
  position: absolute;
  top: -5px;
  right: -5px;
  background: #ff5722;
  color: white;
  border-radius: 50%;
  width: 20px;
  height: 20px;
  font-size: 0.7rem;
  display: flex;
  align-items: center;
  justify-content: center;
}
</style>

```

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

```python
import platform
from pathlib import Path

import pytest

from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language


@pytest.mark.perl
@pytest.mark.skipif(platform.system() == "Windows", reason="Perl::LanguageServer does not support native Windows operation")
class TestPerlLanguageServer:
    """
    Tests for Perl::LanguageServer integration.

    Perl::LanguageServer provides comprehensive LSP support for Perl including:
    - Document symbols (functions, variables)
    - Go to definition (including cross-file)
    - Find references (including cross-file) - this was not available in PLS
    """

    @pytest.mark.parametrize("language_server", [Language.PERL], indirect=True)
    @pytest.mark.parametrize("repo_path", [Language.PERL], indirect=True)
    def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
        """Test that the language server starts and stops successfully."""
        # The fixture already handles start and stop
        assert language_server.is_running()
        assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()

    @pytest.mark.parametrize("language_server", [Language.PERL], indirect=True)
    def test_document_symbols(self, language_server: SolidLanguageServer) -> None:
        """Test that document symbols are correctly identified."""
        # Request document symbols
        all_symbols, _ = language_server.request_document_symbols("main.pl").get_all_symbols_and_roots()

        assert all_symbols, "Expected to find symbols in main.pl"
        assert len(all_symbols) > 0, "Expected at least one symbol"

        # DEBUG: Print all symbols
        print("\n=== All symbols in main.pl ===")
        for s in all_symbols:
            line = s.get("range", {}).get("start", {}).get("line", "?")
            print(f"Line {line}: {s.get('name')} (kind={s.get('kind')})")

        # Check that we can find function symbols
        function_symbols = [s for s in all_symbols if s.get("kind") == 12]  # 12 = Function/Method
        assert len(function_symbols) >= 2, f"Expected at least 2 functions (greet, use_helper_function), found {len(function_symbols)}"

        function_names = [s.get("name") for s in function_symbols]
        assert "greet" in function_names, f"Expected 'greet' function in symbols, found: {function_names}"
        assert "use_helper_function" in function_names, f"Expected 'use_helper_function' in symbols, found: {function_names}"

    # @pytest.mark.skip(reason="Perl::LanguageServer cross-file definition tracking needs configuration")
    @pytest.mark.parametrize("language_server", [Language.PERL], indirect=True)
    def test_find_definition_across_files(self, language_server: SolidLanguageServer) -> None:
        definition_location_list = language_server.request_definition("main.pl", 17, 0)

        assert len(definition_location_list) == 1
        definition_location = definition_location_list[0]
        print(f"Found definition: {definition_location}")
        assert definition_location["uri"].endswith("helper.pl")
        assert definition_location["range"]["start"]["line"] == 4  # add method on line 2 (0-indexed 1)

    @pytest.mark.parametrize("language_server", [Language.PERL], indirect=True)
    def test_find_references_across_files(self, language_server: SolidLanguageServer) -> None:
        """Test finding references to a function across multiple files."""
        reference_locations = language_server.request_references("helper.pl", 4, 5)

        assert len(reference_locations) >= 2, f"Expected at least 2 references to helper_function, found {len(reference_locations)}"

        main_pl_refs = [ref for ref in reference_locations if ref["uri"].endswith("main.pl")]
        assert len(main_pl_refs) >= 2, f"Expected at least 2 references in main.pl, found {len(main_pl_refs)}"

        main_pl_lines = sorted([ref["range"]["start"]["line"] for ref in main_pl_refs])
        assert 17 in main_pl_lines, f"Expected reference at line 18 (0-indexed 17), found: {main_pl_lines}"
        assert 20 in main_pl_lines, f"Expected reference at line 21 (0-indexed 20), found: {main_pl_lines}"

```

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

```yaml
ignore_all_files_in_gitignore: true

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

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


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

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

project_name: "serena"
languages:
- python
- typescript
included_optional_tools: []
encoding: utf-8

```

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

```python
import fnmatch
import logging
import os
import sys
import zipfile
from pathlib import Path
from typing import Optional

log = logging.getLogger(__name__)


class SafeZipExtractor:
    """
    A utility class for extracting ZIP archives safely.

    Features:
    - Handles long file paths on Windows
    - Skips files that fail to extract, continuing with the rest
    - Creates necessary directories automatically
    - Optional include/exclude pattern filters
    """

    def __init__(
        self,
        archive_path: Path,
        extract_dir: Path,
        verbose: bool = True,
        include_patterns: Optional[list[str]] = None,
        exclude_patterns: Optional[list[str]] = None,
    ) -> None:
        """
        Initialize the SafeZipExtractor.

        :param archive_path: Path to the ZIP archive file
        :param extract_dir: Directory where files will be extracted
        :param verbose: Whether to log status messages
        :param include_patterns: List of glob patterns for files to extract (None = all files)
        :param exclude_patterns: List of glob patterns for files to skip
        """
        self.archive_path = Path(archive_path)
        self.extract_dir = Path(extract_dir)
        self.verbose = verbose
        self.include_patterns = include_patterns or []
        self.exclude_patterns = exclude_patterns or []

    def extract_all(self) -> None:
        """
        Extract all files from the archive, skipping any that fail.
        """
        if not self.archive_path.exists():
            raise FileNotFoundError(f"Archive not found: {self.archive_path}")

        if self.verbose:
            log.info(f"Extracting from: {self.archive_path} to {self.extract_dir}")

        with zipfile.ZipFile(self.archive_path, "r") as zip_ref:
            for member in zip_ref.infolist():
                if self._should_extract(member.filename):
                    self._extract_member(zip_ref, member)
                elif self.verbose:
                    log.info(f"Skipped: {member.filename}")

    def _should_extract(self, filename: str) -> bool:
        """
        Determine whether a file should be extracted based on include/exclude patterns.

        :param filename: The file name from the archive
        :return: True if the file should be extracted
        """
        # If include_patterns is set, only extract if it matches at least one pattern
        if self.include_patterns:
            if not any(fnmatch.fnmatch(filename, pattern) for pattern in self.include_patterns):
                return False

        # If exclude_patterns is set, skip if it matches any pattern
        if self.exclude_patterns:
            if any(fnmatch.fnmatch(filename, pattern) for pattern in self.exclude_patterns):
                return False

        return True

    def _extract_member(self, zip_ref: zipfile.ZipFile, member: zipfile.ZipInfo) -> None:
        """
        Extract a single member from the archive with error handling.

        :param zip_ref: Open ZipFile object
        :param member: ZipInfo object representing the file
        """
        try:
            target_path = self.extract_dir / member.filename

            # Ensure directory structure exists
            target_path.parent.mkdir(parents=True, exist_ok=True)

            # Handle long paths on Windows
            final_path = self._normalize_path(target_path)

            # Extract file
            with zip_ref.open(member) as source, open(final_path, "wb") as target:
                target.write(source.read())

            if self.verbose:
                log.info(f"Extracted: {member.filename}")

        except Exception as e:
            log.error(f"Failed to extract {member.filename}: {e}")

    @staticmethod
    def _normalize_path(path: Path) -> Path:
        """
        Adjust path to handle long paths on Windows.

        :param path: Original path
        :return: Normalized path
        """
        if sys.platform.startswith("win"):
            return Path(rf"\\?\{os.path.abspath(path)}")
        return path  # type: ignore


# Example usage:
# extractor = SafeZipExtractor(
#     archive_path=Path("file.nupkg"),
#     extract_dir=Path("extract_dir"),
#     include_patterns=["*.dll", "*.xml"],
#     exclude_patterns=["*.pdb"]
# )
# extractor.extract_all()

```

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

```yaml
language_backend: LSP
# the language backend to use for code understanding and manipulation.
# Possible values are:
#  * LSP: Use the language server protocol (LSP), spawning freely available language servers
#      via the SolidLSP library that is part of Serena.
#  * JetBrains: Use the Serena plugin in your JetBrains IDE.
#      (requires the plugin to be installed and the project being worked on to be open
#      in your IDE).

gui_log_window: False
# whether to open a graphical window with Serena's logs.
# This is mainly supported on Windows and (partly) on Linux; not available on macOS.
# If you prefer a browser-based tool, use the `web_dashboard` option instead.
# Further information: https://oraios.github.io/serena/02-usage/060_dashboard.html
#
# Being able to inspect logs is useful both for troubleshooting and for monitoring the tool calls,
# especially when using the agno playground, since the tool calls are not always shown,
# and the input params are never shown in the agno UI.
# When used as MCP server for Claude Desktop, the logs are primarily for troubleshooting.
# Note: unfortunately, the various entities starting the Serena server or agent do so in
# mysterious ways, often starting multiple instances of the process without shutting down
# previous instances. This can lead to multiple log windows being opened, and only the last
# window being updated. Since we can't control how agno or Claude Desktop start Serena,
# we have to live with this limitation for now.

web_dashboard: True
# whether to open the Serena web dashboard (which will be accessible through your web browser) that
# provides access to Serena's configuration and state as well as the current session logs.
# The web dashboard is supported on all platforms.
# Further information: https://oraios.github.io/serena/02-usage/060_dashboard.html

web_dashboard_listen_address: 127.0.0.1
# The address where the web dashboard will listen on

web_dashboard_open_on_launch: True
# whether to open a browser window with the web dashboard when Serena starts (provided that web_dashboard
# is enabled).
# If set to false, you can still open the dashboard manually by
# a) telling the LLM to "open the dashboard" (provided that the open_dashboard tool is enabled) or by
# b) manually navigating to http://localhost:24282/dashboard/ in your web browser (actual port
#    may be higher if you have multiple instances running; try ports 24283, 24284, etc.)
# See also: https://oraios.github.io/serena/02-usage/060_dashboard.html

log_level: 20
# the minimum log level for the GUI log window and the dashboard (10 = debug, 20 = info, 30 = warning, 40 = error)

trace_lsp_communication: False
# whether to trace the communication between Serena and the language servers.
# This is useful for debugging language server issues.

ls_specific_settings: {}
# Added on 23.08.2025
# Advanced configuration option allowing to configure language server implementation specific options. Maps the language
# (same entry as in project.yml) to the options.
# 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.
# No documentation on options means no options are available.

tool_timeout: 240
# timeout, in seconds, after which tool executions are terminated

excluded_tools: []
# list of tools to be globally excluded

included_optional_tools: []
# list of optional tools (which are disabled by default) to be included

default_max_tool_answer_chars: 150000
# Used as default for tools where the apply method has a default maximal answer length.
# Even though the value of the max_answer_chars can be changed when calling the tool, it may make sense to adjust this default
# through the global configuration.

token_count_estimator: CHAR_COUNT
# the name of the token count estimator to use for tool usage statistics.
# See the `RegisteredTokenCountEstimator` enum for available options.
#
# By default, a very naive character count estimator is used, which simply counts the number of characters.
# You can configure this to TIKTOKEN_GPT4 to use a local tiktoken-based estimator for GPT-4 (will download tiktoken
# data files on first run), or ANTHROPIC_CLAUDE_SONNET_4 which will use the (free of cost) Anthropic API to
# estimate the token count using the Claude Sonnet 4 tokenizer.

# The list of registered project paths (updated automatically).
projects: []

```

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

```markdown
# Lessons Learned

In this document we briefly collect what we have learned while developing and using Serena,
what works well and what doesn't.

## What Worked

### Separate Tool Logic From MCP Implementation

MCP is just another protocol, one should let the details of it creep into the application logic.
The official docs suggest using function annotations to define tools and prompts. While that may be
useful for small projects to get going fast, it is not wise for more serious projects. In Serena,
all tools are defined independently and then converted to instances of `MCPTool` using our `make_tool`
function.

### Autogenerated PromptFactory

Prompt templates are central for most LLM applications, so one needs good representations of them in the code,
while at the same time they often need to be customizable and exposed to users. In Serena we address these conflicting 
needs by defining prompt templates (in jinja format) in separate yamls that users can easily modify and by autogenerated
a `PromptFactory` class with meaningful method and parameter names from these yamls. The latter is committed to our code.
We separated out the generation logic into the [interprompt](/src/interprompt/README.md) subpackage that can be used as a library.

### Tempfiles and Snapshots for Testing of Editing Tools

We test most aspects of Serena by having a small "project" for each supported language in `tests/resources`.
For the editing tools, which would change the code in these projects, we use tempfiles to copy over the code.
The pretty awesome [syrupy](https://github.com/syrupy-project/syrupy) pytest plugin helped in developing
snapshot tests.

### Dashboard and GUI for Logging

It is very useful to know what the MCP Server is doing. We collect and display logs in a GUI or a web dashboard,
which helps a lot in seeing what's going on and in identifying any issues.

### Unrestricted Bash Tool

We know it's not particularly safe to permit unlimited shell commands outside a sandbox, but we did quite some
evaluations and so far... nothing bad has happened. Seems like the current versions of the AI overlords rarely want to execute `sudo rm - rf /`.
Still, we are working on a safer approach as well as better integration with sandboxing.

### Multilspy

The [multilspy](https://github.com/microsoft/multilspy/) project helped us a lot in getting started and stands at the core of Serena.
Many more well known python implementations of language servers were subpar in code quality and design (for example, missing types).

### Developing Serena with Serena

We clearly notice that the better the tool gets, the easier it is to make it even better

## Prompting

### Shouting and Emotive Language May Be Needed

When developing the `ReplaceRegexTool` we were initially not able to make Claude 4 (in Claude Desktop) use wildcards to save on output tokens. Neither
examples nor explicit instructions helped. It was only after adding 

```
IMPORTANT: REMEMBER TO USE WILDCARDS WHEN APPROPRIATE! I WILL BE VERY UNHAPPY IF YOU WRITE LONG REGEXES WITHOUT USING WILDCARDS INSTEAD!
```

to the initial instructions and to the tool description that Claude finally started following the instructions.

## What Didn't Work

### Lifespan Handling by MCP Clients

The MCP technology is clearly very green. Even though there is a lifespan context in the MCP SDK,
many clients, including Claude Desktop, fail to properly clean up, leaving zombie processes behind.
We mitigate this through the GUI window and the dashboard, so the user sees whether Serena is running
and can terminate it there.

### Trusting Asyncio

Running multiple asyncio apps led to non-deterministic 
event loop contamination and deadlocks, which were very hard to debug
and understand. We solved this with a large hammer, by putting all asyncio apps into a separate
process. It made the code much more complex and slightly enhanced RAM requirements, but it seems
like that was the only way to reliably overcome asyncio deadlock issues.

### Cross-OS Tkinter GUI

Different OS have different limitations when it comes to starting a window or dealing with Tkinter
installations. This was so messy to get right that we pivoted to a web-dashboard instead

### Editing Based on Line Numbers

Not only are LLMs notoriously bad in counting, but also the line numbers change after edit operations,
and LLMs are also often too dumb to understand that they should update the line numbers information they had
received before. We pivoted to string-matching and symbol-name based editing.
```

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

```vue
<script setup lang="ts">
import { computed, ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useCalculatorStore } from '@/stores/calculator'
import type { HistoryEntry } from '@/types'

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

// Use storeToRefs to get reactive references to store state
const { recentHistory, hasHistory, currentValue, operation } = storeToRefs(store)

// Local ref for display options
const showFullHistory = ref(false)
const maxHistoryItems = ref(5)

// Computed property for filtered history
const displayedHistory = computed((): HistoryEntry[] => {
  if (showFullHistory.value) {
    return recentHistory.value
  }
  return recentHistory.value.slice(0, maxHistoryItems.value)
})

// Format date for display
const formatDate = (date: Date): string => {
  return new Date(date).toLocaleTimeString()
}

// Format the current calculation status
const currentCalculation = computed((): string => {
  if (operation.value && store.previousValue !== null) {
    const opSymbol = {
      add: '+',
      subtract: '-',
      multiply: '×',
      divide: '÷'
    }[operation.value]
    return `${store.previousValue} ${opSymbol} ${currentValue.value}`
  }
  return currentValue.value.toString()
})

// Toggle history view
const toggleHistoryView = () => {
  showFullHistory.value = !showFullHistory.value
}

// Clear history handler
const clearHistory = () => {
  store.clearHistory()
}

// Check if history is empty
const isHistoryEmpty = computed((): boolean => {
  return !hasHistory.value
})
</script>

<template>
  <div class="calculator-display">
    <div class="current-calculation">
      <h3>Current Calculation</h3>
      <div class="calculation-value">
        {{ currentCalculation }}
      </div>
    </div>

    <div class="history-section">
      <div class="history-header">
        <h3>History</h3>
        <button
          v-if="hasHistory"
          @click="toggleHistoryView"
          class="btn-toggle"
        >
          {{ showFullHistory ? 'Show Less' : 'Show All' }}
        </button>
        <button
          v-if="hasHistory"
          @click="clearHistory"
          class="btn-clear-history"
        >
          Clear History
        </button>
      </div>

      <div v-if="isHistoryEmpty" class="empty-history">
        No calculations yet
      </div>

      <div v-else class="history-list">
        <div
          v-for="(entry, index) in displayedHistory"
          :key="`${entry.timestamp}-${index}`"
          class="history-item"
        >
          <span class="expression">{{ entry.expression }}</span>
          <span class="result">= {{ entry.result }}</span>
          <span class="timestamp">{{ formatDate(entry.timestamp) }}</span>
        </div>
      </div>

      <div v-if="!showFullHistory && recentHistory.length > maxHistoryItems" class="history-count">
        Showing {{ maxHistoryItems }} of {{ recentHistory.length }} entries
      </div>
    </div>
  </div>
</template>

<style scoped>
.calculator-display {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  padding: 1rem;
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.current-calculation {
  padding: 1rem;
  background: #e3f2fd;
  border-radius: 4px;
}

.current-calculation h3 {
  margin: 0 0 0.5rem 0;
  font-size: 1rem;
  color: #1976d2;
}

.calculation-value {
  font-size: 1.5rem;
  font-weight: bold;
  color: #333;
}

.history-section {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.history-header {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.history-header h3 {
  margin: 0;
  flex: 1;
}

.btn-toggle,
.btn-clear-history {
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.9rem;
}

.btn-toggle {
  background: #2196f3;
  color: white;
}

.btn-toggle:hover {
  background: #1976d2;
}

.btn-clear-history {
  background: #f44336;
  color: white;
}

.btn-clear-history:hover {
  background: #da190b;
}

.empty-history {
  padding: 2rem;
  text-align: center;
  color: #999;
  font-style: italic;
}

.history-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.history-item {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: 1rem;
  padding: 0.75rem;
  background: #f5f5f5;
  border-radius: 4px;
  align-items: center;
}

.expression {
  font-weight: 500;
}

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

.timestamp {
  font-size: 0.8rem;
  color: #666;
  text-align: right;
}

.history-count {
  text-align: center;
  font-size: 0.9rem;
  color: #666;
  padding: 0.5rem;
}
</style>

```

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

```python
"""
Example demonstrating user management with the test_repo module.

This example showcases:
- Creating and managing users
- Using various object types and relationships
- Type annotations and complex Python patterns
"""

import logging
from dataclasses import dataclass
from typing import Any

from test_repo.models import User, create_user_object
from test_repo.services import UserService

# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


@dataclass
class UserStats:
    """Statistics about user activity."""

    user_id: str
    login_count: int = 0
    last_active_days: int = 0
    engagement_score: float = 0.0

    def is_active(self) -> bool:
        """Check if the user is considered active."""
        return self.last_active_days < 30


class UserManager:
    """Example class demonstrating complex user management."""

    def __init__(self, service: UserService):
        self.service = service
        self.active_users: dict[str, User] = {}
        self.user_stats: dict[str, UserStats] = {}

    def register_user(self, name: str, email: str, roles: list[str] | None = None) -> User:
        """Register a new user."""
        logger.info(f"Registering new user: {name} ({email})")
        user = self.service.create_user(name=name, email=email, roles=roles)
        self.active_users[user.id] = user
        self.user_stats[user.id] = UserStats(user_id=user.id)
        return user

    def get_user(self, user_id: str) -> User | None:
        """Get a user by ID."""
        if user_id in self.active_users:
            return self.active_users[user_id]

        # Try to fetch from service
        user = self.service.get_user(user_id)
        if user:
            self.active_users[user.id] = user
        return user

    def update_user_stats(self, user_id: str, login_count: int, days_since_active: int) -> None:
        """Update statistics for a user."""
        if user_id not in self.user_stats:
            self.user_stats[user_id] = UserStats(user_id=user_id)

        stats = self.user_stats[user_id]
        stats.login_count = login_count
        stats.last_active_days = days_since_active

        # Calculate engagement score based on activity
        engagement = (100 - min(days_since_active, 100)) * 0.8
        engagement += min(login_count, 20) * 0.2
        stats.engagement_score = engagement

    def get_active_users(self) -> list[User]:
        """Get all active users."""
        active_user_ids = [user_id for user_id, stats in self.user_stats.items() if stats.is_active()]
        return [self.active_users[user_id] for user_id in active_user_ids if user_id in self.active_users]

    def get_user_by_email(self, email: str) -> User | None:
        """Find a user by their email address."""
        for user in self.active_users.values():
            if user.email == email:
                return user
        return None


# Example function demonstrating type annotations
def process_user_data(users: list[User], include_inactive: bool = False, transform_func: callable | None = None) -> dict[str, Any]:
    """Process user data with optional transformations."""
    result: dict[str, Any] = {"users": [], "total": 0, "admin_count": 0}

    for user in users:
        if transform_func:
            user_data = transform_func(user.to_dict())
        else:
            user_data = user.to_dict()

        result["users"].append(user_data)
        result["total"] += 1

        if "admin" in user.roles:
            result["admin_count"] += 1

    return result


def main():
    """Main function demonstrating the usage of UserManager."""
    # Initialize service and manager
    service = UserService()
    manager = UserManager(service)

    # Register some users
    admin = manager.register_user("Admin User", "[email protected]", ["admin"])
    user1 = manager.register_user("Regular User", "[email protected]", ["user"])
    user2 = manager.register_user("Another User", "[email protected]", ["user"])

    # Update some stats
    manager.update_user_stats(admin.id, 100, 5)
    manager.update_user_stats(user1.id, 50, 10)
    manager.update_user_stats(user2.id, 10, 45)  # Inactive user

    # Get active users
    active_users = manager.get_active_users()
    logger.info(f"Active users: {len(active_users)}")

    # Process user data
    user_data = process_user_data(active_users, transform_func=lambda u: {**u, "full_name": u.get("name", "")})

    logger.info(f"Processed {user_data['total']} users, {user_data['admin_count']} admins")

    # Example of calling create_user directly
    external_user = create_user_object(id="ext123", name="External User", email="[email protected]", roles=["external"])
    logger.info(f"Created external user: {external_user.name}")


if __name__ == "__main__":
    main()

```

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

```python
"""
Example demonstrating user management with the test_repo module.

This example showcases:
- Creating and managing users
- Using various object types and relationships
- Type annotations and complex Python patterns
"""

import logging
from dataclasses import dataclass
from typing import Any

from test_repo.models import User, create_user_object
from test_repo.services import UserService

# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


@dataclass
class UserStats:
    """Statistics about user activity."""

    user_id: str
    login_count: int = 0
    last_active_days: int = 0
    engagement_score: float = 0.0

    def is_active(self) -> bool:
        """Check if the user is considered active."""
        return self.last_active_days < 30


class UserManager:
    """Example class demonstrating complex user management."""

    def __init__(self, service: UserService):
        self.service = service
        self.active_users: dict[str, User] = {}
        self.user_stats: dict[str, UserStats] = {}

    def register_user(self, name: str, email: str, roles: list[str] | None = None) -> User:
        """Register a new user."""
        logger.info(f"Registering new user: {name} ({email})")
        user = self.service.create_user(name=name, email=email, roles=roles)
        self.active_users[user.id] = user
        self.user_stats[user.id] = UserStats(user_id=user.id)
        return user

    def get_user(self, user_id: str) -> User | None:
        """Get a user by ID."""
        if user_id in self.active_users:
            return self.active_users[user_id]

        # Try to fetch from service
        user = self.service.get_user(user_id)
        if user:
            self.active_users[user.id] = user
        return user

    def update_user_stats(self, user_id: str, login_count: int, days_since_active: int) -> None:
        """Update statistics for a user."""
        if user_id not in self.user_stats:
            self.user_stats[user_id] = UserStats(user_id=user_id)

        stats = self.user_stats[user_id]
        stats.login_count = login_count
        stats.last_active_days = days_since_active

        # Calculate engagement score based on activity
        engagement = (100 - min(days_since_active, 100)) * 0.8
        engagement += min(login_count, 20) * 0.2
        stats.engagement_score = engagement

    def get_active_users(self) -> list[User]:
        """Get all active users."""
        active_user_ids = [user_id for user_id, stats in self.user_stats.items() if stats.is_active()]
        return [self.active_users[user_id] for user_id in active_user_ids if user_id in self.active_users]

    def get_user_by_email(self, email: str) -> User | None:
        """Find a user by their email address."""
        for user in self.active_users.values():
            if user.email == email:
                return user
        return None


# Example function demonstrating type annotations
def process_user_data(users: list[User], include_inactive: bool = False, transform_func: callable | None = None) -> dict[str, Any]:
    """Process user data with optional transformations."""
    result: dict[str, Any] = {"users": [], "total": 0, "admin_count": 0}

    for user in users:
        if transform_func:
            user_data = transform_func(user.to_dict())
        else:
            user_data = user.to_dict()

        result["users"].append(user_data)
        result["total"] += 1

        if "admin" in user.roles:
            result["admin_count"] += 1

    return result


def main():
    """Main function demonstrating the usage of UserManager."""
    # Initialize service and manager
    service = UserService()
    manager = UserManager(service)

    # Register some users
    admin = manager.register_user("Admin User", "[email protected]", ["admin"])
    user1 = manager.register_user("Regular User", "[email protected]", ["user"])
    user2 = manager.register_user("Another User", "[email protected]", ["user"])

    # Update some stats
    manager.update_user_stats(admin.id, 100, 5)
    manager.update_user_stats(user1.id, 50, 10)
    manager.update_user_stats(user2.id, 10, 45)  # Inactive user

    # Get active users
    active_users = manager.get_active_users()
    logger.info(f"Active users: {len(active_users)}")

    # Process user data
    user_data = process_user_data(active_users, transform_func=lambda u: {**u, "full_name": u.get("name", "")})

    logger.info(f"Processed {user_data['total']} users, {user_data['admin_count']} admins")

    # Example of calling create_user directly
    external_user = create_user_object(id="ext123", name="External User", email="[email protected]", roles=["external"])
    logger.info(f"Created external user: {external_user.name}")


if __name__ == "__main__":
    main()

```

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

```python
"""
Basic tests for R Language Server integration
"""

import os
from pathlib import Path

import pytest

from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language


@pytest.mark.r
class TestRLanguageServer:
    """Test basic functionality of the R language server."""

    @pytest.mark.parametrize("language_server", [Language.R], indirect=True)
    @pytest.mark.parametrize("repo_path", [Language.R], indirect=True)
    def test_server_initialization(self, language_server: SolidLanguageServer, repo_path: Path):
        """Test that the R language server initializes properly."""
        assert language_server is not None
        assert language_server.language_id == "r"
        assert language_server.is_running()
        assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()

    @pytest.mark.parametrize("language_server", [Language.R], indirect=True)
    def test_symbol_retrieval(self, language_server: SolidLanguageServer):
        """Test R document symbol extraction."""
        all_symbols, _root_symbols = language_server.request_document_symbols(os.path.join("R", "utils.R")).get_all_symbols_and_roots()

        # Should find the three exported functions
        function_symbols = [s for s in all_symbols if s.get("kind") == 12]  # Function kind
        assert len(function_symbols) >= 3

        # Check that we found the expected functions
        function_names = {s.get("name") for s in function_symbols}
        expected_functions = {"calculate_mean", "process_data", "create_data_frame"}
        assert expected_functions.issubset(function_names), f"Expected functions {expected_functions} but found {function_names}"

    @pytest.mark.parametrize("language_server", [Language.R], indirect=True)
    def test_find_definition_across_files(self, language_server: SolidLanguageServer):
        """Test finding function definitions across files."""
        analysis_file = os.path.join("examples", "analysis.R")

        # In analysis.R line 7: create_data_frame(n = 50)
        # The function create_data_frame is defined in R/utils.R
        # Find definition of create_data_frame function call (0-indexed: line 6)
        definition_location_list = language_server.request_definition(analysis_file, 6, 17)  # cursor on 'create_data_frame'

        assert definition_location_list, f"Expected non-empty definition_location_list but got {definition_location_list=}"
        assert len(definition_location_list) >= 1
        definition_location = definition_location_list[0]
        assert definition_location["uri"].endswith("utils.R")
        # Definition should be around line 37 (0-indexed: 36) where create_data_frame is defined
        assert definition_location["range"]["start"]["line"] >= 35

    @pytest.mark.parametrize("language_server", [Language.R], indirect=True)
    def test_find_references_across_files(self, language_server: SolidLanguageServer):
        """Test finding function references across files."""
        analysis_file = os.path.join("examples", "analysis.R")

        # Test from usage side: find references to calculate_mean from its usage in analysis.R
        # In analysis.R line 13: calculate_mean(clean_data$value)
        # calculate_mean function call is at line 13 (0-indexed: line 12)
        references = language_server.request_references(analysis_file, 12, 15)  # cursor on 'calculate_mean'

        assert references, f"Expected non-empty references for calculate_mean but got {references=}"

        # Must find the definition in utils.R (cross-file reference)
        reference_files = [ref["uri"] for ref in references]
        assert any(uri.endswith("utils.R") for uri in reference_files), "Cross-file reference to definition in utils.R not found"

        # Verify we actually found the right location in utils.R
        utils_refs = [ref for ref in references if ref["uri"].endswith("utils.R")]
        assert len(utils_refs) >= 1, "Should find at least one reference in utils.R"
        utils_ref = utils_refs[0]
        # Should be around line 6 where calculate_mean is defined (0-indexed: line 5)
        assert (
            utils_ref["range"]["start"]["line"] == 5
        ), f"Expected reference at line 5 in utils.R, got line {utils_ref['range']['start']['line']}"

    def test_file_matching(self):
        """Test that R files are properly matched."""
        from solidlsp.ls_config import Language

        matcher = Language.R.get_source_fn_matcher()

        assert matcher.is_relevant_filename("script.R")
        assert matcher.is_relevant_filename("analysis.r")
        assert not matcher.is_relevant_filename("script.py")
        assert not matcher.is_relevant_filename("README.md")

    def test_r_language_enum(self):
        """Test R language enum value."""
        assert Language.R == "r"
        assert str(Language.R) == "r"

```

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

```python
import shutil
import tempfile
from pathlib import Path

import pytest

from serena.config.serena_config import ProjectConfig
from solidlsp.ls_config import Language


class TestProjectConfigAutogenerate:
    """Test class for ProjectConfig autogeneration functionality."""

    def setup_method(self):
        """Set up test environment before each test method."""
        # Create a temporary directory for testing
        self.test_dir = tempfile.mkdtemp()
        self.project_path = Path(self.test_dir)

    def teardown_method(self):
        """Clean up test environment after each test method."""
        # Remove the temporary directory
        shutil.rmtree(self.test_dir)

    def test_autogenerate_empty_directory(self):
        """Test that autogenerate raises ValueError with helpful message for empty directory."""
        with pytest.raises(ValueError) as exc_info:
            ProjectConfig.autogenerate(self.project_path, save_to_disk=False)

        error_message = str(exc_info.value)
        assert "No source files found" in error_message

    def test_autogenerate_with_python_files(self):
        """Test successful autogeneration with Python source files."""
        # Create a Python file
        python_file = self.project_path / "main.py"
        python_file.write_text("def hello():\n    print('Hello, world!')\n")

        # Run autogenerate
        config = ProjectConfig.autogenerate(self.project_path, save_to_disk=False)

        # Verify the configuration
        assert config.project_name == self.project_path.name
        assert config.languages == [Language.PYTHON]

    def test_autogenerate_with_js_files(self):
        """Test successful autogeneration with JavaScript source files."""
        # Create files for multiple languages
        (self.project_path / "small.js").write_text("console.log('JS');")

        # Run autogenerate - should pick Python as dominant
        config = ProjectConfig.autogenerate(self.project_path, save_to_disk=False)

        assert config.languages == [Language.TYPESCRIPT]

    def test_autogenerate_with_multiple_languages(self):
        """Test autogeneration picks dominant language when multiple are present."""
        # Create files for multiple languages
        (self.project_path / "main.py").write_text("print('Python')")
        (self.project_path / "util.py").write_text("def util(): pass")
        (self.project_path / "small.js").write_text("console.log('JS');")

        # Run autogenerate - should pick Python as dominant
        config = ProjectConfig.autogenerate(self.project_path, save_to_disk=False)

        assert config.languages == [Language.PYTHON]

    def test_autogenerate_saves_to_disk(self):
        """Test that autogenerate can save the configuration to disk."""
        # Create a Go file
        go_file = self.project_path / "main.go"
        go_file.write_text("package main\n\nfunc main() {}\n")

        # Run autogenerate with save_to_disk=True
        config = ProjectConfig.autogenerate(self.project_path, save_to_disk=True)

        # Verify the configuration file was created
        config_path = self.project_path / ".serena" / "project.yml"
        assert config_path.exists()

        # Verify the content
        assert config.languages == [Language.GO]

    def test_autogenerate_nonexistent_path(self):
        """Test that autogenerate raises FileNotFoundError for non-existent path."""
        non_existent = self.project_path / "does_not_exist"

        with pytest.raises(FileNotFoundError) as exc_info:
            ProjectConfig.autogenerate(non_existent, save_to_disk=False)

        assert "Project root not found" in str(exc_info.value)

    def test_autogenerate_with_gitignored_files_only(self):
        """Test autogenerate behavior when only gitignored files exist."""
        # Create a .gitignore that ignores all Python files
        gitignore = self.project_path / ".gitignore"
        gitignore.write_text("*.py\n")

        # Create Python files that will be ignored
        (self.project_path / "ignored.py").write_text("print('ignored')")

        # Should still raise ValueError as no source files are detected
        with pytest.raises(ValueError) as exc_info:
            ProjectConfig.autogenerate(self.project_path, save_to_disk=False)

        assert "No source files found" in str(exc_info.value)

    def test_autogenerate_custom_project_name(self):
        """Test autogenerate with custom project name."""
        # Create a TypeScript file
        ts_file = self.project_path / "index.ts"
        ts_file.write_text("const greeting: string = 'Hello';\n")

        # Run autogenerate with custom name
        custom_name = "my-custom-project"
        config = ProjectConfig.autogenerate(self.project_path, project_name=custom_name, save_to_disk=False)

        assert config.project_name == custom_name
        assert config.languages == [Language.TYPESCRIPT]

```

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

```python
import os
from pathlib import Path

import pytest

from serena.constants import SERENA_MANAGED_DIR_NAME
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.ls_utils import SymbolUtils
from solidlsp.settings import SolidLSPSettings


@pytest.mark.groovy
class TestGroovyLanguageServer:
    @classmethod
    def setup_class(cls):
        """
        Set up test class with Groovy test repository.
        """
        cls.test_repo_path = Path(__file__).parent.parent.parent / "resources" / "repos" / "groovy" / "test_repo"

        if not cls.test_repo_path.exists():
            pytest.skip("Groovy test repository not found")

        # Use JAR path from environment variable
        ls_jar_path = os.environ.get("GROOVY_LS_JAR_PATH")
        if not ls_jar_path or not os.path.exists(ls_jar_path):
            pytest.skip(
                "Groovy Language Server JAR not found. Set GROOVY_LS_JAR_PATH environment variable to run tests.",
                allow_module_level=True,
            )

        # Get JAR options from environment variable
        ls_jar_options = os.environ.get("GROOVY_LS_JAR_OPTIONS", "")
        ls_java_home_path = os.environ.get("GROOVY_LS_JAVA_HOME_PATH")

        groovy_settings = {"ls_jar_path": ls_jar_path, "ls_jar_options": ls_jar_options}
        if ls_java_home_path:
            groovy_settings["ls_java_home_path"] = ls_java_home_path

        # Create language server directly with Groovy-specific settings
        repo_path = str(cls.test_repo_path)
        config = LanguageServerConfig(code_language=Language.GROOVY, ignored_paths=[], trace_lsp_communication=False)

        solidlsp_settings = SolidLSPSettings(
            solidlsp_dir=str(Path.home() / ".serena"),
            project_data_relative_path=SERENA_MANAGED_DIR_NAME,
            ls_specific_settings={Language.GROOVY: groovy_settings},
        )

        cls.language_server = SolidLanguageServer.create(config, repo_path, solidlsp_settings=solidlsp_settings)
        cls.language_server.start()

    @classmethod
    def teardown_class(cls):
        """
        Clean up language server.
        """
        if hasattr(cls, "language_server"):
            cls.language_server.stop()

    def test_find_symbol(self) -> None:
        symbols = self.language_server.request_full_symbol_tree()
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main class not found in symbol tree"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils class not found in symbol tree"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model class not found in symbol tree"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "ModelUser"), "ModelUser class not found in symbol tree"

    def test_find_referencing_class_symbols(self) -> None:
        file_path = os.path.join("src", "main", "groovy", "com", "example", "Utils.groovy")
        refs = self.language_server.request_references(file_path, 3, 6)
        assert any("Main.groovy" in ref.get("relativePath", "") for ref in refs), "Utils should be referenced from Main.groovy"

        file_path = os.path.join("src", "main", "groovy", "com", "example", "Model.groovy")
        symbols = self.language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
        model_symbol = None
        for sym in symbols[0]:
            if sym.get("name") == "com.example.Model" and sym.get("kind") == 5:
                model_symbol = sym
                break
        assert model_symbol is not None, "Could not find 'Model' class symbol in Model.groovy"

        if "selectionRange" in model_symbol:
            sel_start = model_symbol["selectionRange"]["start"]
        else:
            sel_start = model_symbol["range"]["start"]
        refs = self.language_server.request_references(file_path, sel_start["line"], sel_start["character"])

        main_refs = [ref for ref in refs if "Main.groovy" in ref.get("relativePath", "")]
        assert len(main_refs) >= 2, f"Model should be referenced from Main.groovy at least 2 times, found {len(main_refs)}"

        model_user_refs = [ref for ref in refs if "ModelUser.groovy" in ref.get("relativePath", "")]
        assert len(model_user_refs) >= 1, f"Model should be referenced from ModelUser.groovy at least 1 time, found {len(model_user_refs)}"

    def test_overview_methods(self) -> None:
        symbols = self.language_server.request_full_symbol_tree()
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main missing from overview"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils missing from overview"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model missing from overview"
        assert SymbolUtils.symbol_tree_contains_name(symbols, "ModelUser"), "ModelUser missing from overview"

```

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

```python
#!/usr/bin/env python
"""
Main entry point script for the test_repo application.

This script demonstrates how a typical application entry point would be structured,
with command-line arguments, configuration loading, and service initialization.
"""

import argparse
import json
import logging
import os
import sys
from typing import Any

# Add parent directory to path to make imports work
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

from test_repo.models import Item, User
from test_repo.services import ItemService, UserService

# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)


def parse_args():
    """Parse command line arguments."""
    parser = argparse.ArgumentParser(description="Test Repo Application")

    parser.add_argument("--config", type=str, default="config.json", help="Path to configuration file")

    parser.add_argument("--mode", choices=["user", "item", "both"], default="both", help="Operation mode")

    parser.add_argument("--verbose", action="store_true", help="Enable verbose logging")

    return parser.parse_args()


def load_config(config_path: str) -> dict[str, Any]:
    """Load configuration from a JSON file."""
    if not os.path.exists(config_path):
        logger.warning(f"Configuration file not found: {config_path}")
        return {}

    try:
        with open(config_path, encoding="utf-8") as f:
            return json.load(f)
    except json.JSONDecodeError:
        logger.error(f"Invalid JSON in configuration file: {config_path}")
        return {}
    except Exception as e:
        logger.error(f"Error loading configuration: {e}")
        return {}


def create_sample_users(service: UserService, count: int = 3) -> list[User]:
    """Create sample users for demonstration."""
    users = []

    # Create admin user
    admin = service.create_user(name="Admin User", email="[email protected]", roles=["admin"])
    users.append(admin)

    # Create regular users
    for i in range(count - 1):
        user = service.create_user(name=f"User {i + 1}", email=f"user{i + 1}@example.com", roles=["user"])
        users.append(user)

    return users


def create_sample_items(service: ItemService, count: int = 5) -> list[Item]:
    """Create sample items for demonstration."""
    categories = ["Electronics", "Books", "Clothing", "Food", "Other"]
    items = []

    for i in range(count):
        category = categories[i % len(categories)]
        item = service.create_item(name=f"Item {i + 1}", price=10.0 * (i + 1), category=category)
        items.append(item)

    return items


def run_user_operations(service: UserService, config: dict[str, Any]) -> None:
    """Run operations related to users."""
    logger.info("Running user operations")

    # Get configuration
    user_count = config.get("user_count", 3)

    # Create users
    users = create_sample_users(service, user_count)
    logger.info(f"Created {len(users)} users")

    # Demonstrate some operations
    for user in users:
        logger.info(f"User: {user.name} (ID: {user.id})")

        # Access a method to demonstrate method calls
        if user.has_role("admin"):
            logger.info(f"{user.name} is an admin")

    # Lookup a user
    found_user = service.get_user(users[0].id)
    if found_user:
        logger.info(f"Found user: {found_user.name}")


def run_item_operations(service: ItemService, config: dict[str, Any]) -> None:
    """Run operations related to items."""
    logger.info("Running item operations")

    # Get configuration
    item_count = config.get("item_count", 5)

    # Create items
    items = create_sample_items(service, item_count)
    logger.info(f"Created {len(items)} items")

    # Demonstrate some operations
    total_price = 0.0
    for item in items:
        price_display = item.get_display_price()
        logger.info(f"Item: {item.name}, Price: {price_display}")
        total_price += item.price

    logger.info(f"Total price of all items: ${total_price:.2f}")


def main():
    """Main entry point for the application."""
    # Parse command line arguments
    args = parse_args()

    # Configure logging level
    if args.verbose:
        logging.getLogger().setLevel(logging.DEBUG)

    logger.info("Starting Test Repo Application")

    # Load configuration
    config = load_config(args.config)
    logger.debug(f"Loaded configuration: {config}")

    # Initialize services
    user_service = UserService()
    item_service = ItemService()

    # Run operations based on mode
    if args.mode in ("user", "both"):
        run_user_operations(user_service, config)

    if args.mode in ("item", "both"):
        run_item_operations(item_service, config)

    logger.info("Application completed successfully")


item_reference = Item(id="1", name="Item 1", price=10.0, category="Electronics")

if __name__ == "__main__":
    main()

```
Page 2/17FirstPrevNextLast