mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 02:14:49 +00:00
Merge branch 'main' into tray-manager
Conflicts: src/serena/agent.py src/serena/dashboard.py
This commit is contained in:
commit
0da7b6d711
145 files changed
+8922
-1685
No files matched your search
@@ -0,0 +1,4 @@
|
||||
# Force LF line endings for Haxe files across platforms.
|
||||
# The Haxe language server cannot properly parse/index files with CRLF endings, causing it to fail on Windows without this.
|
||||
*.hxml text eol=lf
|
||||
*.hx text eol=lf
|
||||
@@ -0,0 +1,103 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL Advanced"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
schedule:
|
||||
- cron: '38 16 * * 6'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: actions
|
||||
build-mode: none
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
- language: python
|
||||
build-mode: none
|
||||
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Add any setup steps before running the `github/codeql-action/init` action.
|
||||
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
||||
# or others). This is typically only required for manual builds.
|
||||
# - name: Setup runtime (example)
|
||||
# uses: actions/setup-example@v1
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- name: Run manual build steps
|
||||
if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||
'languages you are analyzing, replace this with the commands to build' \
|
||||
'your code, for example:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
@@ -20,6 +20,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Annotate locations with typos
|
||||
uses: codespell-project/codespell-problem-matcher@v1
|
||||
- name: Codespell
|
||||
|
||||
@@ -20,6 +20,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
name: Docs Build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@@ -10,10 +12,6 @@ permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -22,6 +20,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
@@ -47,11 +47,13 @@ jobs:
|
||||
continue-on-error: false
|
||||
|
||||
- name: Upload Pages artifact
|
||||
if: github.ref == 'refs/heads/main'
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: docs/_build
|
||||
|
||||
deploy:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
name: Junie
|
||||
run-name: Junie run ${{ inputs.run_id }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
run_id:
|
||||
description: "id of workflow process"
|
||||
required: true
|
||||
workflow_params:
|
||||
description: "stringified params"
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
call-workflow-passing-data:
|
||||
uses: jetbrains-junie/junie-workflows/.github/workflows/ej-issue.yml@main
|
||||
with:
|
||||
workflow_params: ${{ inputs.workflow_params }}
|
||||
@@ -1,33 +1,23 @@
|
||||
name: Publish Python Package
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag name for the release (e.g., v0.1.0)'
|
||||
required: true
|
||||
default: 'v0.1.0'
|
||||
|
||||
env:
|
||||
# Set this to true manually in the GitHub workflow UI if you want to publish to PyPI
|
||||
# Will always publish to testpypi
|
||||
PUBLISH_TO_PYPI: true
|
||||
push:
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish the serena-agent package
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for trusted publishing
|
||||
contents: write # Required for updating artifact
|
||||
|
||||
contents: write # Required for the GH release
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
@@ -37,18 +27,11 @@ jobs:
|
||||
- name: Build package
|
||||
run: uv build
|
||||
|
||||
- name: Upload artifacts to GitHub Release
|
||||
if: env.PUBLISH_TO_PYPI == 'true'
|
||||
uses: softprops/action-gh-release@v2
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
tag_name: ${{ github.event.inputs.tag || github.ref_name }}
|
||||
files: |
|
||||
dist/*.tar.gz
|
||||
dist/*.whl
|
||||
|
||||
- name: Publish to TestPyPI
|
||||
run: uv publish --index testpypi
|
||||
|
||||
- name: Publish to PyPI (conditional)
|
||||
if: env.PUBLISH_TO_PYPI == 'true'
|
||||
run: uv publish
|
||||
@@ -6,6 +6,9 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -20,7 +23,7 @@ jobs:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
python-version: ["3.11"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Free disk space
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
@@ -386,6 +389,12 @@ jobs:
|
||||
- name: Install ansible-core and ansible-lint (for Ansible language server tests)
|
||||
shell: bash
|
||||
run: uv run pip install ansible-core ansible-lint
|
||||
- name: Install Haxe
|
||||
# The Haxe LS binary (server.js) is auto-downloaded and runs on Node.js,
|
||||
# but it delegates to the Haxe compiler for code analysis at runtime.
|
||||
uses: krdlab/setup-haxe@v2
|
||||
with:
|
||||
haxe-version: 4.3.7
|
||||
- name: Install Elm
|
||||
shell: bash
|
||||
run: npm install -g elm@0.19.1-6
|
||||
|
||||
+3
-1
@@ -216,7 +216,7 @@ tmp/
|
||||
.vscode/
|
||||
|
||||
# Claude settings
|
||||
.claude/settings.local.json
|
||||
/.claude
|
||||
|
||||
# Elixir
|
||||
/test/resources/repos/elixir/test_repo/deps
|
||||
@@ -270,3 +270,5 @@ cabal.project.local*
|
||||
|
||||
zz-misc/
|
||||
vue-implementation/
|
||||
|
||||
news/news.json
|
||||
+24
-24
@@ -48,41 +48,41 @@ read_only: false
|
||||
# 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.
|
||||
# * `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.
|
||||
# * `delete_memory`: 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.
|
||||
# * `edit_memory`: Replaces content matching a regular expression in a memory.
|
||||
# * `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).
|
||||
# * `find_file`: Finds files in the given relative paths
|
||||
# * `find_referencing_symbols`: Finds symbols that reference the given symbol using the language server backend
|
||||
# * `find_symbol`: Performs a global (or local) search using the language server backend.
|
||||
# * `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.
|
||||
# * `initial_instructions`: Provides instructions Serena usage (i.e. the 'Serena Instructions Manual')
|
||||
# for clients that do not read the initial instructions when the MCP server is connected.
|
||||
# * `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.
|
||||
# * `list_memories`: List available memories. Any memory can be read using the `read_memory` tool.
|
||||
# * `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.
|
||||
# * `read_memory`: 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.
|
||||
# * `rename_memory`: Renames or moves a memory. Moving between project and global scope is supported
|
||||
# (e.g., renaming "global/foo" to "bar" moves it from global to project scope).
|
||||
# * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities.
|
||||
# For JB, we use a separate tool.
|
||||
# * `replace_content`: Replaces content in a file (optionally using regular expressions).
|
||||
# * `replace_symbol_body`: Replaces the full definition of a symbol using the language server backend.
|
||||
# * `safe_delete_symbol`:
|
||||
# * `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.
|
||||
# * `write_memory`: 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.
|
||||
excluded_tools: []
|
||||
|
||||
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
Relevant information about the project is in .serena/memories. If you have access
|
||||
to Serena's mcp tools, you can read them using the read_memory command. Otherwise
|
||||
you can just read them using normal file reading tools.
|
||||
+91
-23
@@ -1,20 +1,85 @@
|
||||
# Latest
|
||||
# Unreleased (main)
|
||||
|
||||
Status of the `main` branch. Changes prior to the next official version change will appear here.
|
||||
|
||||
* Memories:
|
||||
* Add `ignored_memory_patterns` configuration option (regex-based) to completely exclude
|
||||
matching memories from listing, reading, and writing. Configurable at both global
|
||||
(`serena_config.yml`) and project (`project.yml`) level, merged additively.
|
||||
Useful for projects with large numbers of archived memory files that inflate
|
||||
`activate_project` output.
|
||||
* General:
|
||||
- Support `serena --version` CLI command for displaying the current version #1347
|
||||
- Fix: Check for ignored path ignored `.git` folder only at the top level, not in every subdirectory (`Project._is_ignored_relative_path`) #1350
|
||||
- `GetSymbolsOverviewTool`: ignored paths were not respected in LSP variant (fix in `SolidLanguageServer`)
|
||||
- Fix: Duplicate comments in re-saved YAML configuration files #1285
|
||||
|
||||
* New language support:
|
||||
* Add Solidity language server support (`Language.SOLIDITY`) using the
|
||||
Nomic Foundation `@nomicfoundation/solidity-language-server`. Automatically
|
||||
installed via npm. Supports `.sol` files with go-to-definition, find references,
|
||||
document symbols, hover, and diagnostics. Works best with a `foundry.toml` or
|
||||
`hardhat.config.js` in the project root.
|
||||
* JetBrains:
|
||||
- `Move` and `SafeDelete` tools: transform empty string to None (counteracts client errors)
|
||||
|
||||
* Dependencies:
|
||||
- `pywebview`: Switch back to official release (new version 6.2) #1253
|
||||
|
||||
|
||||
* Language Servers:
|
||||
- Fix: clangd capability checks now tolerate valid initialize response shape differences and invalidate cached C++ document symbols when clangd/compile commands context changes #1359
|
||||
- Fix: `rename_symbol` for Vue files now correctly propagates edits to the TypeScript server, enabling cross-file renames in `.vue` files
|
||||
- Fix: Lean4 stale cache — empty document symbol responses (returned before `lake build` completes) are no longer persisted, preventing symbols from being permanently hidden #1356
|
||||
|
||||
|
||||
# v1.1.2 (2026-04-14)
|
||||
|
||||
* General:
|
||||
- Support environment variable `SERENA_USAGE_REPORTING` (set to `false` to disable usage reporting)
|
||||
- Extended the list of always ignored directories (by language servers) with common cases.
|
||||
- Improve exposed toolset: With mode switching no longer being a feature, we now fully apply tool exclusions
|
||||
defined by modes when in a single-project context (limiting exposed tools to a minimum)
|
||||
- Fix: When scanning for `.gitignore` files, the presence of files that could not be made relative
|
||||
to the project root would cause the scan to fail. #1317
|
||||
|
||||
* Dashboard:
|
||||
- Fix handling of read news, saving each read news entry separately #1338
|
||||
|
||||
* JetBrains:
|
||||
- Improve handling of `relative_path` parameter
|
||||
- Improve its documentation to avoid usage errors
|
||||
- Replace escaped characters in `relative_path` with their unescaped counterparts (< and >)
|
||||
- `FindSymbolTool`: Force `search_deps=True` if `relative_path` pertains to external dependencies.
|
||||
|
||||
* Language Servers:
|
||||
- Add mSL (mIRC Scripting Language) support (custom pygls-based language server; symbols, references, definitions)
|
||||
- Fix initialisation issues in Vue language server #1333
|
||||
|
||||
# v1.1.1 (2026-04-12)
|
||||
|
||||
* General:
|
||||
- Enable cert verification for HTTPS request to oraios-software.de #1320
|
||||
|
||||
* JetBrains:
|
||||
- `JetBrainsRenameTool` can now also rename occurrences in comments and text.
|
||||
|
||||
* Language Servers:
|
||||
- Fix Dart LSP returning only symbol name as body instead of full method body.
|
||||
|
||||
|
||||
# v1.1.0 (2026-04-11)
|
||||
|
||||
* General:
|
||||
- **Major**: Add commands for hooks and documentation of recommended setup. Consider setting up the [recommended hooks](https://oraios.github.io/serena/02-usage/030_clients.html) !
|
||||
- Add `serena init` and `serena setup` commands
|
||||
- Rework installation instructions, switching to releases on pypi for distribution. Please update your mcp startup commands!
|
||||
- Add minimal usage data collection on startup (only Serena version, language backend, OS, dashboard enabled status; no personally identifiable information)
|
||||
- Fix: git commit id in Serena version strings was incorrect
|
||||
|
||||
* Language Servers:
|
||||
- Add support for Haxe via vshaxe/haxe-language-server. Requires Haxe compiler 3.4.0+ and Node.js. Auto-discovered from the vshaxe VSCode extension or configurable via `ls_path` in `ls_specific_settings`.
|
||||
- Add Crystal language support (uses [Crystalline](https://github.com/elbywan/crystalline) language server)
|
||||
- Fix: Reactivation of the same project restarted language servers #1280
|
||||
|
||||
* JetBrains:
|
||||
- `JetBrainsFindReferencingSymbolTool`: Include context lines (when using plugin version 2023.2.15+)
|
||||
|
||||
* Dashboard:
|
||||
- Add version display
|
||||
- Fix: Dashboard viewer (Windows): Add a parent monitoring thread to ensure termination.
|
||||
Some clients would terminate the MCP server in a way that did not ensure proper termination.
|
||||
- Fix: Manual server shutdown triggered by GUI tool/dashboard not cleaning everything up.
|
||||
|
||||
# v1.0.0 (2026-04-03)
|
||||
|
||||
* General:
|
||||
* Add monorepo/multi-language support
|
||||
@@ -38,9 +103,9 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
* Log page now has save (downloads a snapshot) and clear (resets log view) buttons alongside the existing copy button
|
||||
* Language server backend:
|
||||
* New two-tier caching of language server document symbols and considerable performance improvements surrounding symbol retrieval/indexing
|
||||
* Allow passing language server specific settings through `ls_specific_settings` field (in `serena_config.yml`)
|
||||
* Allow passing language server-specific settings through `ls_specific_settings` field (in `serena_config.yml`)
|
||||
* Add the JetBrains language backend as an alternative to language servers
|
||||
* Improve management of the Serena projects
|
||||
* Improve management of Serena projects
|
||||
* Facilitate project activation based on the current directory (through the `--project-from-cwd` parameter)
|
||||
* Add notion of a "single-project context" (flag `single_project`), allowing user-defined contexts to behave
|
||||
like the built-in `ide-assistant` context (where the available tools are restricted to ones required by the active
|
||||
@@ -49,16 +114,19 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
locations outside of the project folder, thus improving support for read-only projects.
|
||||
* Add support for `project.local.yml` for local overrides that should not be versioned
|
||||
* Various fixes related to indexing, special paths and determination of ignored paths
|
||||
|
||||
* Client support:
|
||||
* New mode `oaicompat-agent` and extensions enhancing OpenAI tool compatibility, permitting Serena to work with llama.cpp
|
||||
* Memories:
|
||||
* Add support for global memories (shared across projects)
|
||||
* Add `read_only_memory_patterns` configuration option
|
||||
* Add `ignored_memory_patterns` configuration option
|
||||
* Improved client support, e.g. new mode `oaicompat-agent` and extensions enhancing OpenAI tool compatibility
|
||||
|
||||
* Tools:
|
||||
* Symbol information (hover, docstring, quick-info) is now provided as part of `find_symbol` and related tool responses.
|
||||
* Additional symbol meta-information (hover, docstring, quick-info) is now provided as part of `find_symbol` and related tool responses.
|
||||
* Added `QueryProjectTool` and `ListQueryableProjectTool` (see above)
|
||||
* Added `RenameSymbolTool` for renaming symbols across the codebase (if LS supports this operation).
|
||||
* Replaced `ReplaceRegexTool` with `ReplaceContentTool`, which supports both plain text and regex-based replacements
|
||||
(and which requires no escaping in the replacement text, making it more robust)
|
||||
(and which requires no escaping in the replacement text, making it more robust)
|
||||
* Add JetBrains tools which leverage the corresponding JetBrains language backend through our plugin
|
||||
* Decreased `TOOL_DEFAULT_MAX_ANSWER_LENGTH` to be in accordance with (below) typical max-tokens configurations
|
||||
|
||||
* Language support:
|
||||
@@ -90,9 +158,9 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
* **Add support for MATLAB** via the official MathWorks MATLAB Language Server. Requires MATLAB R2021b or later and Node.js. Set `MATLAB_PATH` environment variable or configure `matlab_path` in `ls_specific_settings`. Supports .m, .mlx, and .mlapp files with code completion, diagnostics, go-to-definition, find references, document symbols, formatting, and rename.
|
||||
* **Add support for Pascal** via the official Pascal Language Server.
|
||||
* **C/C++ alternate LS (ccls)**: Add experimental, opt-in support for ccls as an alternative backend to clangd. Enable via `cpp_ccls` in project configuration. Requires `ccls` installed and ideally a `compile_commands.json` at repo root.
|
||||
* **Add support for Solidity** via the Nomic Foundation `@nomicfoundation/solidity-language-server` (automatically installed via npm)
|
||||
|
||||
|
||||
# 0.1.4
|
||||
# v0.1.4 (2025-08-15)
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -136,7 +204,7 @@ Fixes:
|
||||
default shell reconfiguration imposed by Claude Code)
|
||||
* Additional wait for initialization in C# language server before requesting references, allowing cross-file references to be found.
|
||||
|
||||
# 0.1.3
|
||||
# v0.1.3 (2025-07-22)
|
||||
|
||||
## Summary
|
||||
|
||||
|
||||
@@ -1,114 +1,3 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Development Commands
|
||||
|
||||
**Essential Commands (use these exact commands):**
|
||||
- `uv run poe format` - Format code (RUFF) - ONLY allowed formatting command
|
||||
- `uv run poe type-check` - Run mypy type checking - ONLY allowed type checking command
|
||||
- `uv run poe test` - Run tests with default markers (excludes java/rust by default)
|
||||
- `uv run poe test -m "python or go"` - Run specific language tests
|
||||
- `uv run poe test -m vue` - Run Vue tests
|
||||
- `uv run poe lint` - Check code style without fixing
|
||||
|
||||
**Test Markers:**
|
||||
Available pytest markers for selective testing:
|
||||
- `python`, `go`, `java`, `rust`, `typescript`, `vue`, `php`, `perl`, `powershell`, `csharp`, `elixir`, `terraform`, `clojure`, `swift`, `bash`, `ruby`, `ruby_solargraph`
|
||||
- `snapshot` - for symbolic editing operation tests
|
||||
|
||||
**Project Management:**
|
||||
- `uv run serena-mcp-server` - Start MCP server from project root
|
||||
- `uv run index-project` - Index project for faster tool performance
|
||||
|
||||
**Always run format, type-check, and test before completing any task.**
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
Serena is a dual-layer coding agent toolkit:
|
||||
|
||||
### Core Components
|
||||
|
||||
**1. SerenaAgent (`src/serena/agent.py`)**
|
||||
- Central orchestrator managing projects, tools, and user interactions
|
||||
- Coordinates language servers, memory persistence, and MCP server interface
|
||||
- Manages tool registry and context/mode configurations
|
||||
|
||||
**2. SolidLanguageServer (`src/solidlsp/ls.py`)**
|
||||
- Unified wrapper around Language Server Protocol (LSP) implementations
|
||||
- Provides language-agnostic interface for symbol operations
|
||||
- Handles caching, error recovery, and multiple language server lifecycle
|
||||
|
||||
**3. Tool System (`src/serena/tools/`)**
|
||||
- **file_tools.py** - File system operations, search, regex replacements
|
||||
- **symbol_tools.py** - Language-aware symbol finding, navigation, editing
|
||||
- **memory_tools.py** - Project knowledge persistence and retrieval
|
||||
- **config_tools.py** - Project activation, mode switching
|
||||
- **workflow_tools.py** - Onboarding and meta-operations
|
||||
|
||||
**4. Configuration System (`src/serena/config/`)**
|
||||
- **Contexts** - Define tool sets for different environments (desktop-app, agent, ide-assistant)
|
||||
- **Modes** - Operational patterns (planning, editing, interactive, one-shot)
|
||||
- **Projects** - Per-project settings and language server configs
|
||||
|
||||
### Language Support Architecture
|
||||
|
||||
Each supported language has:
|
||||
1. **Language Server Implementation** in `src/solidlsp/language_servers/`
|
||||
2. **Runtime Dependencies** - Automatic language server downloads when needed
|
||||
3. **Test Repository** in `test/resources/repos/<language>/`
|
||||
4. **Test Suite** in `test/solidlsp/<language>/`
|
||||
|
||||
### Memory & Knowledge System
|
||||
|
||||
- **Markdown-based storage** in `.serena/memories/` directories
|
||||
- **Project-specific knowledge** persistence across sessions
|
||||
- **Contextual retrieval** based on relevance
|
||||
- **Onboarding support** for new projects
|
||||
|
||||
## Development Patterns
|
||||
|
||||
### Adding New Languages
|
||||
1. Create language server class in `src/solidlsp/language_servers/`
|
||||
2. Add to Language enum in `src/solidlsp/ls_config.py`
|
||||
3. Update factory method in `src/solidlsp/ls.py`
|
||||
4. Create test repository in `test/resources/repos/<language>/`
|
||||
5. Write test suite in `test/solidlsp/<language>/`
|
||||
6. Add pytest marker to `pyproject.toml`
|
||||
|
||||
### Adding New Tools
|
||||
1. Inherit from `Tool` base class in `src/serena/tools/tools_base.py`
|
||||
2. Implement required methods and parameter validation
|
||||
3. Register in appropriate tool registry
|
||||
4. Add to context/mode configurations
|
||||
|
||||
### Testing Strategy
|
||||
- Language-specific tests use pytest markers
|
||||
- Symbolic editing operations have snapshot tests
|
||||
- Integration tests in `test_serena_agent.py`
|
||||
- Test repositories provide realistic symbol structures
|
||||
|
||||
## Configuration Hierarchy
|
||||
|
||||
Configuration is loaded from (in order of precedence):
|
||||
1. Command-line arguments to `serena-mcp-server`
|
||||
2. Project-specific `.serena/project.yml`
|
||||
3. User config `~/.serena/serena_config.yml`
|
||||
4. Active modes and contexts
|
||||
|
||||
## Key Implementation Notes
|
||||
|
||||
- **Symbol-based editing** - Uses LSP for precise code manipulation
|
||||
- **Caching strategy** - Reduces language server overhead
|
||||
- **Error recovery** - Automatic language server restart on crashes
|
||||
- **Multi-language support** - 19 languages with LSP integration (including Vue)
|
||||
- **MCP protocol** - Exposes tools to AI agents via Model Context Protocol
|
||||
- **Async operation** - Non-blocking language server interactions
|
||||
|
||||
## Working with the Codebase
|
||||
|
||||
- Project uses Python 3.11 with `uv` for dependency management
|
||||
- Strict typing with mypy, formatted with ruff
|
||||
- Language servers run as separate processes with LSP communication
|
||||
- Memory system enables persistent project knowledge
|
||||
- Context/mode system allows workflow customization
|
||||
Relevant information about the project is in .serena/memories. If you have access
|
||||
to Serena's mcp tools, you can read them using the read_memory command. Otherwise
|
||||
you can just read them using normal file reading tools.
|
||||
+9
-1
@@ -22,12 +22,20 @@ See the corresponding [memory](.serena/memories/adding_new_language_support_guid
|
||||
|
||||
You can install a virtual environment with the required as follows
|
||||
|
||||
1. Create a new virtual environment: `uv venv`
|
||||
1. Create a new virtual environment: `uv venv -p 3.13`
|
||||
2. Activate the environment:
|
||||
* On Linux/Unix/macOS or Windows with Git Bash: `source .venv/bin/activate`
|
||||
* On Windows outside of Git Bash: `.venv\Scripts\activate.bat` (in cmd/ps) or `source .venv/Scripts/activate` (in git-bash)
|
||||
3. Install the required packages with all extras: `uv sync --extra dev`
|
||||
|
||||
## Local Installation as Tool
|
||||
|
||||
To install Serena as a local tool, run
|
||||
|
||||
```shell
|
||||
uv tool install --reinstall -p 3.13 .
|
||||
```
|
||||
|
||||
## Poe Tasks
|
||||
|
||||
We use poe to execute development tasks:
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Developer Instructions
|
||||
|
||||
## Python Environment & Development Tools
|
||||
|
||||
See [the contributing guide](CONTRIBUTING.md) for instructions on setting up your development environment
|
||||
and tools for formatting and type checking.
|
||||
|
||||
## Release Process
|
||||
|
||||
1. Ensure clean git status
|
||||
2. Set version for release, e.g.
|
||||
|
||||
python scripts/bump_version.py --patch
|
||||
python scripts/bump_version.py --minor
|
||||
|
||||
This also creates the git tag.
|
||||
3. Push to GitHub, triggering the release process.
|
||||
|
||||
git push
|
||||
git push --tags
|
||||
@@ -1,49 +1,213 @@
|
||||
<p align="center" style="text-align:center">
|
||||
<p align="center" style="text-align:center;">
|
||||
<img src="resources/serena-logo.svg#gh-light-mode-only" style="width:500px">
|
||||
<img src="resources/serena-logo-dark-mode.svg#gh-dark-mode-only" style="width:500px">
|
||||
</p>
|
||||
|
||||
* :rocket: Serena is a powerful **coding agent toolkit** capable of turning an LLM into a fully-featured agent that works **directly on your codebase**.
|
||||
Unlike most other tools, it is not tied to an LLM, framework or an interface, making it easy to use it in a variety of ways.
|
||||
* :wrench: Serena provides essential **semantic code retrieval and editing tools** that are akin to an IDE's capabilities, extracting code entities at the symbol level and exploiting relational structure. When combined with an existing coding agent, these tools greatly enhance (token) efficiency.
|
||||
* :free: Serena is **free & open-source**, enhancing the capabilities of LLMs you already have access to free of charge.
|
||||
<h3 align="center">
|
||||
Serena is the IDE for your coding agent.
|
||||
</h3>
|
||||
|
||||
You can think of Serena as providing IDE-like tools to your LLM/coding agent.
|
||||
With it, the agent no longer needs to read entire files, perform grep-like searches or basic string replacements to find the right parts of the code and to edit code.
|
||||
Instead, it can use code-centric tools like `find_symbol`, `find_referencing_symbols` and `insert_after_symbol`.
|
||||
<div align="center">
|
||||
<a href="https://discord.com/invite/cVUNQmnV4r"><img src="https://img.shields.io/badge/discord-join-5865F2?style=flat-square&labelColor=0a0e14&logo=discord&logoColor=5865F2" alt="discord"></a>
|
||||
<a href="https://github.com/oraios/serena/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-b0e8ff?style=flat-square&labelColor=0a0e14" alt="license"></a>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<p align="center">
|
||||
<em>Serena is under active development! See the latest updates, upcoming features, and lessons learned to stay up to date.</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/Updates-1e293b?style=flat&logo=rss&logoColor=white&labelColor=1e293b" alt="Changelog" /></a>
|
||||
<a href="lessons_learned.md"><img src="https://img.shields.io/badge/Lessons-Learned-7c4700?style=flat&logo=readthedocs&logoColor=white&labelColor=7c4700" alt="Lessons Learned" /></a>
|
||||
</p>
|
||||
* Serena provides essential **semantic code retrieval, editing and refactoring tools** that are akin to an IDE's capabilities,
|
||||
operating at the symbol level and exploiting relational structure.
|
||||
* It integrates with any client/LLM via the model context protocol (**MCP**).
|
||||
|
||||
Serena's **agent-first tool design** involves robust high-level abstractions, distinguishing it from
|
||||
approaches that rely on low-level concepts like line numbers or primitive search patterns.
|
||||
|
||||
> [!TIP]
|
||||
> The [**Serena JetBrains plugin**](#the-serena-jetbrains-plugin) has been released!
|
||||
Practically, this means that your agent operates **faster, more efficiently and more reliably**, especially in larger and
|
||||
more complex codebases.
|
||||
|
||||
## LLM Integration
|
||||
> [!IMPORTANT]
|
||||
> Do not install Serena via an MCP or plugin marketplace! They contain outdated and suboptimal installation commands.
|
||||
> Instead, follow our [Quick Start](#quick-start) instructions.
|
||||
|
||||
Serena provides the necessary [tools](https://oraios.github.io/serena/01-about/035_tools.html) for coding workflows, but an LLM is required to do the actual work,
|
||||
orchestrating tool use.
|
||||
## What Our "End Users" Say
|
||||
|
||||
In general, Serena can be integrated with an LLM in several ways:
|
||||
While it is humans who download and set up Serena, our end users are essentially AI agents.
|
||||
As the ones actually applying Serena's tools, they are in the best position to evaluate Serena.
|
||||
|
||||
* by using the **model context protocol (MCP)**.
|
||||
Serena provides an MCP server which integrates with
|
||||
* Claude Code and Claude Desktop,
|
||||
* terminal-based clients like Codex, Gemini-CLI, Qwen3-Coder, rovodev, OpenHands CLI and others,
|
||||
* IDEs like VSCode, Cursor or IntelliJ,
|
||||
* Extensions like Cline or Roo Code
|
||||
* Local clients like [OpenWebUI](https://docs.openwebui.com/openapi-servers/mcp), [Jan](https://jan.ai/docs/mcp-examples/browser/browserbase#enable-mcp), [Agno](https://docs.agno.com/introduction/playground) and others
|
||||
* by using [mcpo to connect it to ChatGPT](docs/03-special-guides/serena_on_chatgpt.md) or other clients that don't support MCP but do support tool calling via OpenAPI.
|
||||
* by incorporating Serena's tools into an agent framework of your choice, as illustrated [here](docs/03-special-guides/custom_agent.md).
|
||||
Serena's tool implementation is decoupled from the framework-specific code and can thus easily be adapted to any agent framework.
|
||||
We crafted an unbiased evaluation prompt that leads the agent to perform ~20 routine coding tasks,
|
||||
representative of everyday development work,
|
||||
in order to estimate the value added by Serena's tools when used alongside its own built-ins.
|
||||
|
||||
Here's a one-sentence summary of what the agents had to say:
|
||||
|
||||
**Opus 4.6 (high) in Claude Code on a large Python codebase:**
|
||||
> "Serena's IDE-backed semantic tools are the single most impactful addition to my toolkit – cross-file renames, moves, and reference lookups that
|
||||
would cost me 8–12 careful, error-prone steps collapse into one atomic call, and I would absolutely ask any developer I work with to set them up."
|
||||
|
||||
**GPT 5.4 (high) in Codex CLI on a Java codebase:**
|
||||
> "As a coding AI agent, I would ask my owner to add Serena because it gives me the missing IDE-level understanding of symbols, references, and
|
||||
refactorings, turning fragile text surgery into calmer, faster, more confident code changes where semantics matter."
|
||||
|
||||
**GPT 5.4 (medium) in Copilot CLI on a large, multi-language monorepo:**
|
||||
> "As a coding agent, I’d absolutely ask my owner to add Serena because it makes me noticeably sharper and calmer on
|
||||
real code – especially symbol-aware navigation, cross-file refactors, and monorepo dependency jumps – while I still lean
|
||||
on built-ins for tiny text edits and non-code work."
|
||||
|
||||
Different agents in different settings independently converge on the same verdict.
|
||||
|
||||
_Give your agent the tools it has been asking for and add Serena MCP to your client!_
|
||||
|
||||
See our [documentation](https://oraios.github.io/serena/04-evaluation/000_evaluation-intro.html) for the full methodology and much more detailed evaluation results, or run your own evaluation on a project of your choice.
|
||||
|
||||
|
||||
## How Serena Works
|
||||
|
||||
Serena provides the necessary [tools](https://oraios.github.io/serena/01-about/035_tools.html) for coding workflows,
|
||||
but an LLM is required to do the actual work, orchestrating tool use.
|
||||
|
||||
Serena can extend the functionality of your existing AI client via the **model context protocol (MCP)**.
|
||||
Most modern AI chat clients directly support MCP, including
|
||||
* terminal-based clients like Claude Code, Codex, OpenCode, or Gemini-CLI,
|
||||
* IDEs and IDE assistant plugins for VSCode, Cursor and JetBrains IDEs (Copilot, Junie, JetBrains AI Assistant, etc.),
|
||||
* desktop and web clients like Claude Desktop, Codex App, or OpenWebUI.
|
||||
|
||||
<img src="resources/serena-block-diagram.svg">
|
||||
|
||||
To connect the Serena MCP server to your client, you either
|
||||
* provide the client with a launch command that allows it to start the MCP server, or
|
||||
* start the Serena MCP server yourself in HTTP mode and provide the client with the URL.
|
||||
|
||||
Serena's tools are powered by two alternative language intelligence backends:
|
||||
* With the SolidLSP backend (default), language servers for the selected programming languages will be started
|
||||
automatically for your project.
|
||||
* With the JetBrains backend, the Serena JetBrains plugin must be installed in your IDE and
|
||||
the project you want to work on must open and set up.
|
||||
|
||||
See the [Quick Start](#quick-start) section below for information on how to get started.
|
||||
|
||||
## Programming Language Support & Semantic Analysis Capabilities
|
||||
|
||||
Serena provides a set of versatile code querying and editing functionalities
|
||||
based on symbolic understanding of the code.
|
||||
Equipped with these capabilities, your agent discovers and edits code just like a seasoned developer
|
||||
making use of an IDE's capabilities would.
|
||||
Serena can efficiently find the right context and do the right thing even in very large and
|
||||
complex projects!
|
||||
|
||||
There are two alternative technologies powering these capabilities:
|
||||
|
||||
* **Language servers** implementing the language server protocol (LSP) — the free/open-source alternative
|
||||
which is used by default.
|
||||
* The **Serena JetBrains Plugin**, which leverages the powerful code analysis and editing
|
||||
capabilities of your JetBrains IDE (paid plugin; free trial available).
|
||||
|
||||
You can choose either of these backends depending on your preferences and requirements.
|
||||
|
||||
### Language Servers
|
||||
|
||||
Serena incorporates a powerful abstraction layer for the integration of language servers that implement the language server protocol (LSP).
|
||||
The underlying language servers are typically open-source projects or at least freely available for use.
|
||||
|
||||
When using Serena's language server backend, we provide **support for over 40 programming languages**, including
|
||||
AL, Ansible, Bash, C#, C/C++, Clojure, Crystal, Dart, Elixir, Elm, Erlang, Fortran, F#, GLSL, Go, Groovy, Haskell, Haxe, HLSL, Java, JavaScript, Julia, Kotlin, Lean 4, Lua, Luau, Markdown, MATLAB, mSL, Nix, OCaml, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, Solidity, Swift, TOML, TypeScript, WGSL, YAML, and Zig.
|
||||
|
||||
### The Serena JetBrains Plugin
|
||||
|
||||
The paid Serena JetBrains Plugin (free trial available)
|
||||
leverages the powerful code analysis capabilities of your JetBrains IDE.
|
||||
The plugin naturally supports all programming languages and frameworks that are supported by JetBrains IDEs,
|
||||
including IntelliJ IDEA, PyCharm, Android Studio, WebStorm, PhpStorm, RubyMine, GoLand, and potentially others (Rider and CLion are unsupported though).
|
||||
|
||||
<a href="https://plugins.jetbrains.com/plugin/28946-serena/"><img src="docs/_static/images/jetbrains-marketplace-button.png"></a>
|
||||
|
||||
See our [documentation page](https://oraios.github.io/serena/02-usage/025_jetbrains_plugin.html) for further details and instructions on how to apply the plugin.
|
||||
|
||||
## Features
|
||||
|
||||
Serena provides a wide range of tools for efficient code retrieval, editing and refactoring, as well as
|
||||
a memory system for long-lived agent workflows.
|
||||
|
||||
Given its large scope, Serena adapts to your needs by offering a multi-layered configuration system.
|
||||
|
||||
<details>
|
||||
<summary>Details</summary>
|
||||
|
||||
### Retrieval
|
||||
|
||||
Serena's retrieval tools allow agents to explore codebases at the symbol level, understanding structure and relationships
|
||||
without reading entire files.
|
||||
|
||||
| Capability | Language Servers | JetBrains Plugin |
|
||||
|----------------------------------|------------------|------------------|
|
||||
| find symbol | yes | yes |
|
||||
| symbol overview (file outline) | yes | yes |
|
||||
| find referencing symbols | yes | yes |
|
||||
| search in project dependencies | -- | yes |
|
||||
| type hierarchy | -- | yes |
|
||||
| find declaration | -- | yes |
|
||||
| find implementations | -- | yes |
|
||||
| query external projects | yes | yes |
|
||||
|
||||
### Refactoring
|
||||
|
||||
Without precise refactoring tools, agents are forced to resort to unreliable and expensive search and replace operations.
|
||||
|
||||
| Capability | Language Servers | JetBrains Plugin |
|
||||
|-------------------------------------------|--------------------|-----------------------------------|
|
||||
| rename | yes (only symbols) | yes (symbols, files, directories) |
|
||||
| move (symbol, file, directory) | -- | yes |
|
||||
| inline | -- | yes |
|
||||
| propagate deletions (remove unused code) | -- | yes |
|
||||
|
||||
### Symbolic Editing
|
||||
|
||||
Serena's symbolic editing tools are less error-prone and much more token-efficient than typical alternatives.
|
||||
|
||||
| Capability | Language Servers | JetBrains Plugin |
|
||||
|------------------------|-------------------|------------------|
|
||||
| replace symbol body | yes | yes |
|
||||
| insert after symbol | yes | yes |
|
||||
| insert before symbol | yes | yes |
|
||||
| safe delete | yes | yes |
|
||||
|
||||
### Basic Features
|
||||
|
||||
Beyond its semantic capabilities, Serena includes a set of basic utilities for completeness.
|
||||
When Serena is used inside an agentic harness such as Claude Code or Codex, these tools are typically disabled by default,
|
||||
since the surrounding harness already provides overlapping file, search, and shell capabilities.
|
||||
|
||||
- **`search_for_pattern`** – flexible regex search across the codebase
|
||||
- **`replace_content`** – agent-optimised regex-based and literal text replacement
|
||||
- **`list_dir` / `find_file`** – directory listing and file search
|
||||
- **`read_file`** – read files or file chunks
|
||||
- **`execute_shell_command`** – run shell commands (e.g. builds, tests, linters)
|
||||
|
||||
### Memory Management
|
||||
|
||||
A memory system is elemental to long-lived agent workflows, especially when knowledge is to be shared across
|
||||
sessions, users and projects.
|
||||
Despite its simplicity, we received positive feedback from many users who tend to combine Serena's memory management system with their
|
||||
agent's internal system (e.g., `AGENTS.md` files).
|
||||
It can easily be disabled if you prefer to use something else.
|
||||
|
||||
### Configurability
|
||||
|
||||
Active tools, tool descriptions, prompts, language backend details and many other aspects of Serena
|
||||
can be flexibly configured on a per-case basis by simply adjusting a few lines of YAML.
|
||||
To achieve this, Serena offers multiple levels of (composable) configuration:
|
||||
|
||||
* global configuration
|
||||
* MCP launch command (CLI) configuration
|
||||
* per-project configuration (with local overrides)
|
||||
* execution context-specific configuration (e.g. for particular clients)
|
||||
* dynamically composable configuration fragments (modes)
|
||||
|
||||
</details>
|
||||
|
||||
## Serena in Action
|
||||
|
||||
<details>
|
||||
<summary>Demonstrations</summary>
|
||||
|
||||
#### Demonstration 1: Efficient Operation in Claude Code
|
||||
|
||||
A demonstration of Serena efficiently retrieving and editing code within Claude Code, thereby saving tokens and time. Efficient operations are not only useful for saving costs, but also for generally improving the generated code's quality. This effect may be less pronounced in very small projects, but often becomes of crucial importance in larger ones.
|
||||
@@ -56,59 +220,34 @@ A demonstration of Serena implementing a small feature for itself (a better log
|
||||
Note how Serena's tools enable Claude to find and edit the right symbols.
|
||||
|
||||
https://github.com/user-attachments/assets/6eaa9aa1-610d-4723-a2d6-bf1e487ba753
|
||||
|
||||
## Programming Language Support & Semantic Analysis Capabilities
|
||||
|
||||
Serena provides a set of versatile code querying and editing functionalities
|
||||
based on symbolic understanding of the code.
|
||||
Equipped with these capabilities, Serena discovers and edits code just like a seasoned developer
|
||||
making use of an IDE's capabilities would.
|
||||
Serena can efficiently find the right context and do the right thing even in very large and
|
||||
complex projects!
|
||||
|
||||
There are two alternative technologies powering these capabilities:
|
||||
|
||||
* **Language servers** implementing the language server Protocol (LSP) — the free/open-source alternative.
|
||||
* **The Serena JetBrains Plugin**, which leverages the powerful code analysis and editing
|
||||
capabilities of your JetBrains IDE.
|
||||
|
||||
You can choose either of these backends depending on your preferences and requirements.
|
||||
|
||||
### Language Servers
|
||||
|
||||
Serena incorporates a powerful abstraction layer for the integration of language servers
|
||||
that implement the language server protocol (LSP).
|
||||
The underlying language servers are typically open-source projects (like Serena) or at least freely available for use.
|
||||
|
||||
With Serena's LSP library, we provide **support for over 40 programming languages**, including
|
||||
AL, Ansible, Bash, C#, C/C++, Clojure, Dart, Elixir, Elm, Erlang, Fortran, F# (currently with some bugs), GLSL, Go, Groovy (partial support), Haskell, HLSL, Java, Javascript, Julia, Kotlin, Lean 4, Lua, Luau, Markdown, MATLAB, Nix, OCaml, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, Solidity, Swift, TOML, TypeScript, WGSL, YAML, and Zig.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Some language servers require additional dependencies to be installed; see the [Language Support](https://oraios.github.io/serena/01-about/020_programming-languages.html) page for details.
|
||||
|
||||
### The Serena JetBrains Plugin
|
||||
|
||||
As an alternative to language servers, the [Serena JetBrains Plugin](https://plugins.jetbrains.com/plugin/28946-serena/)
|
||||
leverages the powerful code analysis capabilities of your JetBrains IDE.
|
||||
The plugin naturally supports all programming languages and frameworks that are supported by JetBrains IDEs,
|
||||
including IntelliJ IDEA, PyCharm, Android Studio, WebStorm, PhpStorm, RubyMine, GoLand, and potentially others (Rider and CLion are unsupported though).
|
||||
|
||||
<a href="https://plugins.jetbrains.com/plugin/28946-serena/"><img src="docs/_static/images/jetbrains-marketplace-button.png"></a>
|
||||
|
||||
The plugin offers the most robust and most powerful Serena experience.
|
||||
See our [documentation page](https://oraios.github.io/serena/02-usage/025_jetbrains_plugin.html) for further details and instructions.
|
||||
</details>
|
||||
|
||||
## Quick Start
|
||||
|
||||
**Prerequisites**. Serena is managed by *uv*. If you don’t already have it, you need to [install uv](https://docs.astral.sh/uv/getting-started/installation/) before proceeding.
|
||||
**Prerequisites**. Serena is managed by *uv*, and [installing uv](https://docs.astral.sh/uv/getting-started/installation/) is the only required prerequisite.
|
||||
|
||||
**Starting the MCP Server**. The easiest way to start the Serena MCP server is by running the latest version from GitHub using uvx.
|
||||
Issue this command to see available options:
|
||||
> [!NOTE]
|
||||
> When using the language server backend, some additional dependencies may need to be installed to support certain languages;
|
||||
> see the [Language Support](https://oraios.github.io/serena/01-about/020_programming-languages.html) page for details.
|
||||
|
||||
**Install Serena**. Serena is installed via uv as follows:
|
||||
|
||||
```bash
|
||||
uvx --from git+https://github.com/oraios/serena serena start-mcp-server --help
|
||||
uv tool install -p 3.13 serena-agent@latest --prerelease=allow
|
||||
```
|
||||
|
||||
After successful installation, the command `serena` should be available in your shell.
|
||||
|
||||
**Initialise Serena**. To initialise Serena and verify that your setup works correctly, simply run:
|
||||
|
||||
```bash
|
||||
serena init
|
||||
```
|
||||
|
||||
By default, this will set up Serena to use the language server backend. To use the JetBrains backend instead, add the parameters `-b JetBrains`
|
||||
(see the [JetBrains Plugin documentation page](https://oraios.github.io/serena/02-usage/025_jetbrains_plugin.html) for additional usage details).
|
||||
Either way, you should receive a success message indicating that Serena has been initialised successfully.
|
||||
|
||||
**Configuring Your Client**. To connect Serena to your preferred MCP client, you typically need to [configure a launch command in your client](https://oraios.github.io/serena/02-usage/030_clients.html).
|
||||
Follow the link for specific instructions on how to set up Serena for Claude Code, Codex, Claude Desktop, MCP-enabled IDEs and other clients (such as local and web-based GUIs).
|
||||
|
||||
@@ -124,74 +263,8 @@ Follow the link for specific instructions on how to set up Serena for Claude Cod
|
||||
|
||||
Please refer to the [user guide](https://oraios.github.io/serena/02-usage/000_intro.html) for detailed instructions on how to use Serena effectively.
|
||||
|
||||
## Community Feedback
|
||||
|
||||
Most users report that Serena has strong positive effects on the results of their coding agents, even when used within
|
||||
very capable agents like Claude Code. Serena is often described to be a [game changer](https://www.reddit.com/r/ClaudeAI/comments/1lfsdll/try_out_serena_mcp_thank_me_later/), providing an enormous [productivity boost](https://www.reddit.com/r/ClaudeCode/comments/1mguoia/absolutely_insane_improvement_of_claude_code).
|
||||
|
||||
Serena excels at navigating and manipulating complex codebases, providing tools that support precise code retrieval and editing in the presence of large, strongly structured codebases.
|
||||
However, when dealing with tasks that involve only very few/small files, you may not benefit from including Serena on top of your existing coding agent.
|
||||
In particular, when writing code from scratch, Serena will not provide much value initially, as the more complex structures that Serena handles more gracefully than simplistic, file-based approaches are yet to be created.
|
||||
|
||||
Several videos and blog posts have talked about Serena:
|
||||
|
||||
* YouTube:
|
||||
* [AI Labs](https://www.youtube.com/watch?v=wYWyJNs1HVk&t=1s)
|
||||
* [Yo Van Eyck](https://www.youtube.com/watch?v=UqfxuQKuMo8&t=45s)
|
||||
* [JeredBlu](https://www.youtube.com/watch?v=fzPnM3ySmjE&t=32s)
|
||||
|
||||
* Blog posts:
|
||||
* [Serena's Design Principles](https://medium.com/@souradip1000/deconstructing-serenas-mcp-powered-semantic-code-understanding-architecture-75802515d116)
|
||||
* [Serena with Claude Code (in Japanese)](https://blog.lai.so/serena/)
|
||||
* [Turning Claude Code into a Development Powerhouse](https://robertmarshall.dev/blog/turning-claude-code-into-a-development-powerhouse/)
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
### Sponsors
|
||||
|
||||
We are very grateful to our [sponsors](https://github.com/sponsors/oraios) who help us drive Serena's development. The core team
|
||||
(the founders of [Oraios AI](https://oraios-ai.de/)) put in a lot of work in order to turn Serena into a useful open source project.
|
||||
So far, there is no business model behind this project, and sponsors are our only source of income from it.
|
||||
|
||||
Sponsors help us dedicating more time to the project, managing contributions, and working on larger features (like better tooling based on more advanced
|
||||
LSP features, VSCode integration, debugging via the DAP, and several others).
|
||||
If you find this project useful to your work, or would like to accelerate the development of Serena, consider becoming a sponsor.
|
||||
|
||||
We are proud to announce that the Visual Studio Code team, together with Microsoft’s Open Source Programs Office and GitHub Open Source
|
||||
have decided to sponsor Serena with a one-time contribution!
|
||||
|
||||
<p align="center">
|
||||
<img src="resources/vscode_sponsor_logo.png" alt="Visual Studio Code sponsor logo" width="220">
|
||||
</p>
|
||||
|
||||
### Community Contributions
|
||||
|
||||
A significant part of Serena, especially support for various languages, was contributed by the open source community.
|
||||
We are very grateful for the many contributors who made this possible and who played an important role in making Serena
|
||||
what it is today.
|
||||
|
||||
### Technologies
|
||||
We built Serena on top of multiple existing open-source technologies, the most important ones being:
|
||||
|
||||
1. [multilspy](https://github.com/microsoft/multilspy).
|
||||
A library which wraps language server implementations and adapts them for interaction via Python.
|
||||
It provided the basis for our library Solid-LSP (`src/solidlsp`).
|
||||
Solid-LSP provides pure synchronous LSP calls and extends the original library with the symbolic logic
|
||||
that Serena required.
|
||||
2. [Python MCP SDK](https://github.com/modelcontextprotocol/python-sdk)
|
||||
3. All the language servers that we use through Solid-LSP.
|
||||
|
||||
Without these projects, Serena would not have been possible (or would have been significantly more difficult to build).
|
||||
|
||||
## Customizing and Extending Serena
|
||||
|
||||
It is straightforward to extend Serena's AI functionality with your own ideas.
|
||||
Simply implement a new tool by subclassing
|
||||
`serena.agent.Tool` and implement the `apply` method with a signature
|
||||
that matches the tool's requirements.
|
||||
Once implemented, `SerenaAgent` will automatically have access to the new tool.
|
||||
|
||||
It is also relatively straightforward to add [support for a new programming language](/.serena/memories/adding_new_language_support_guide.md).
|
||||
|
||||
We look forward to seeing what the community will come up with!
|
||||
For details on contributing, see [contributing guidelines](/CONTRIBUTING.md).
|
||||
@@ -1 +1,4 @@
|
||||
/035_tools.md
|
||||
# auto-generated files
|
||||
/000_intro.md
|
||||
/025_features.md
|
||||
/035_tools.md
|
||||
@@ -1,14 +0,0 @@
|
||||
# About Serena
|
||||
|
||||
* Serena is a powerful **coding agent toolkit** capable of turning an LLM into a fully-featured agent that works
|
||||
**directly on your codebase**.
|
||||
Unlike most other tools, it is not tied to an LLM, framework or an interface, making it easy to use it in a variety of ways.
|
||||
* Serena provides essential **semantic code retrieval and editing tools** that are akin to an IDE's capabilities,
|
||||
extracting code entities at the symbol level and exploiting relational structure.
|
||||
When combined with an existing coding agent, these tools greatly enhance (token) efficiency.
|
||||
* Serena is **free & open-source**, enhancing the capabilities of LLMs you already have access to free of charge.
|
||||
|
||||
Therefore, you can think of Serena as providing IDE-like tools to your LLM/coding agent.
|
||||
With it, the agent no longer needs to read entire files, perform grep-like searches or string replacements to find and
|
||||
edit the right code.
|
||||
Instead, it can use code-centred tools like `find_symbol`, `find_referencing_symbols` and `insert_after_symbol`.
|
||||
@@ -1,17 +0,0 @@
|
||||
# LLM Integration
|
||||
|
||||
Serena provides the necessary [tools](035_tools) for coding workflows, but an LLM is required to do the actual work,
|
||||
orchestrating tool use.
|
||||
|
||||
In general, Serena can be integrated with an LLM in several ways:
|
||||
|
||||
* by using the **model context protocol (MCP)**.
|
||||
Serena provides an MCP server which integrates with
|
||||
* Claude Code and Claude Desktop,
|
||||
* Terminal-based clients like Codex, Gemini-CLI, Qwen3-Coder, rovodev, OpenHands CLI and others,
|
||||
* IDEs like VSCode, Cursor or IntelliJ,
|
||||
* Extensions like Cline or Roo Code
|
||||
* Local clients like [OpenWebUI](https://docs.openwebui.com/openapi-servers/mcp), [Jan](https://jan.ai/docs/mcp-examples/browser/browserbase#enable-mcp), [Agno](https://docs.agno.com/introduction/playground) and others
|
||||
* by using [mcpo to connect it to ChatGPT](../03-special-guides/serena_on_chatgpt.md) or other clients that don't support MCP but do support tool calling via OpenAPI.
|
||||
* by incorporating Serena's tools into an agent framework of your choice, as illustrated [here](../03-special-guides/custom_agent).
|
||||
Serena's tool implementation is decoupled from the framework-specific code and can thus easily be adapted to any agent framework.
|
||||
@@ -1,7 +1,7 @@
|
||||
# Language Support
|
||||
|
||||
Serena provides a set of versatile code querying and editing functionalities
|
||||
based on symbolic understanding of the code.
|
||||
based on symbolic understanding of the code across a wide range of programming languages.
|
||||
Equipped with these capabilities, Serena discovers and edits code just like a seasoned developer
|
||||
making use of an IDE's capabilities would.
|
||||
Serena can efficiently find the right context and do the right thing even in very large and
|
||||
@@ -13,8 +13,9 @@ There are two alternative technologies powering these capabilities:
|
||||
* **The Serena JetBrains Plugin**, which leverages the powerful code analysis and editing
|
||||
capabilities of your JetBrains IDE.
|
||||
|
||||
You can choose either of these backends depending on your preferences and requirements.
|
||||
See the [Features](025_features) section for a detailed comparison of the capabilities provided by the JetBrains Plugin vs. language servers.
|
||||
|
||||
(language-servers)=
|
||||
## Language Servers
|
||||
|
||||
Serena incorporates a powerful abstraction layer for the integration of language servers
|
||||
@@ -28,7 +29,7 @@ We currently provide direct, out-of-the-box support for the programming language
|
||||
Some languages require additional installations or setup steps, as noted.
|
||||
|
||||
* **AL**
|
||||
* **Ansible**
|
||||
* **Ansible**
|
||||
(experimental; requires Node.js and npm; automatically installs `@ansible/ansible-language-server`;
|
||||
must be explicitly specified in the `languages` entry in the `project.yml`; requires `ansible` in PATH for full functionality)
|
||||
the upstream `@ansible/ansible-language-server@1.2.3` supports hover, completion, definition,
|
||||
@@ -43,6 +44,9 @@ Some languages require additional installations or setup steps, as noted.
|
||||
for best results, provide a `compile_commands.json` at the repository root;
|
||||
see the [C/C++ Setup Guide](../03-special-guides/cpp_setup) for details.)
|
||||
* **Clojure**
|
||||
* **Crystal**
|
||||
(requires [Crystalline](https://github.com/elbywan/crystalline) language server to be installed and available on PATH;
|
||||
note: Crystalline has limited go-to-definition support and does not support find-references)
|
||||
* **Dart**
|
||||
* **Elixir**
|
||||
(requires Elixir installation; Expert language server is downloaded automatically)
|
||||
@@ -58,8 +62,11 @@ Some languages require additional installations or setup steps, as noted.
|
||||
(requires installation of `gopls`)
|
||||
* **Groovy**
|
||||
(requires local groovy-language-server.jar setup via `GROOVY_LS_JAR_PATH` or configuration)
|
||||
* **Haskell**
|
||||
* **Haskell**
|
||||
(automatically locates HLS via ghcup, stack, or system PATH; supports Stack and Cabal projects)
|
||||
* **Haxe**
|
||||
(requires Haxe compiler 3.4.0+ and Node.js; uses the [vshaxe language server](https://github.com/vshaxe/haxe-language-server);
|
||||
automatically downloaded from Open VSX, or discovered from the vshaxe VSCode extension)
|
||||
* **HLSL / GLSL / WGSL**
|
||||
(uses [shader-language-server](https://github.com/antaalt/shader-sense) (language `hlsl`); automatically downloaded;
|
||||
on macOS, requires Rust toolchain for building from source;
|
||||
@@ -77,6 +84,9 @@ Some languages require additional installations or setup steps, as noted.
|
||||
* **Luau**
|
||||
* **Markdown**
|
||||
(must explicitly enable language `markdown`, primarily useful for documentation-heavy projects)
|
||||
* **mSL** (mIRC Scripting Language)
|
||||
(auto-installed; no external dependencies required — uses a custom pygls-based LSP server shipped with Serena;
|
||||
supports document symbols, workspace symbols, references, and go-to-definition for aliases, events, menus, dialogs, and CTCP handlers in `.mrc` files)
|
||||
* **Nix**
|
||||
(requires nixd installation)
|
||||
* **OCaml**
|
||||
@@ -97,7 +107,7 @@ Some languages require additional installations or setup steps, as noted.
|
||||
(requires [rustup](https://rustup.rs/) - uses rust-analyzer from your toolchain)
|
||||
* **Scala**
|
||||
(requires some [manual setup](../03-special-guides/scala_setup_guide_for_serena); uses Metals LSP)
|
||||
* **Solidity**
|
||||
* **Solidity**
|
||||
(experimental; requires Node.js and npm; automatically installs `@nomicfoundation/solidity-language-server`;
|
||||
works best with a `foundry.toml` or `hardhat.config.js` in the project root)
|
||||
* **Swift**
|
||||
@@ -113,25 +123,15 @@ see Serena's [memory on that](https://github.com/oraios/serena/blob/main/.serena
|
||||
|
||||
## The Serena JetBrains Plugin
|
||||
|
||||
As an alternative to language servers, the [Serena JetBrains Plugin](https://plugins.jetbrains.com/plugin/28946-serena/)
|
||||
leverages the powerful code analysis capabilities of JetBrains IDEs.
|
||||
The plugin naturally supports all programming languages and frameworks that are supported by JetBrains IDEs,
|
||||
including IntelliJ IDEA, PyCharm, Android Studio, WebStorm, PhpStorm, RubyMine, GoLand, and potentially others
|
||||
(Rider and CLion are unsupported though).
|
||||
The [Serena JetBrains Plugin](https://plugins.jetbrains.com/plugin/28946-serena/) leverages the powerful code analysis capabilities of JetBrains IDEs.
|
||||
The plugin naturally supports all programming languages and frameworks that are supported by JetBrains IDEs.
|
||||
|
||||
When using the plugin, Serena connects to an instance of your JetBrains IDE via the plugin. For users who already
|
||||
work in a JetBrains IDE, this means Serena seamlessly integrates with the IDE instance you typically have open anyway,
|
||||
requiring no additional setup or configuration beyond the plugin itself. This approach offers several key advantages:
|
||||
requiring no additional setup or configuration beyond the plugin itself.
|
||||
|
||||
* **External library indexing**: Dependencies and libraries are fully indexed and accessible to Serena
|
||||
* **No additional setup**: No need to download or configure separate language servers
|
||||
* **Enhanced performance**: Faster tool execution thanks to optimized IDE integration
|
||||
* **Multi-language excellence**: First-class support for polyglot projects with multiple languages and frameworks
|
||||
|
||||
Even if you prefer to work in a different code editor, you can still benefit from the JetBrains plugin by running
|
||||
a JetBrains IDE instance (most have free community editions) alongside your preferred editor with your project
|
||||
opened and indexed. Serena will connect to the IDE for code analysis while you continue working in your editor
|
||||
of choice.
|
||||
* See the [JetBrains Plugin documentation](../02-usage/025_jetbrains_plugin) for a high-level overview of its benefits and usage details.
|
||||
* See the [Features](025_features) section for a detailed comparison of the capabilities provided by the JetBrains Plugin vs. language servers.
|
||||
|
||||
```{raw} html
|
||||
<p>
|
||||
@@ -140,5 +140,3 @@ of choice.
|
||||
</a>
|
||||
</p>
|
||||
```
|
||||
|
||||
See the [JetBrains Plugin documentation](../02-usage/025_jetbrains_plugin) for usage details.
|
||||
@@ -0,0 +1,51 @@
|
||||
# Installation
|
||||
|
||||
## Prerequisites
|
||||
|
||||
**Package Manager: uv**
|
||||
|
||||
Serena is managed by `uv`.
|
||||
If you do not have it yet, install it following the instructions [here](https://docs.astral.sh/uv/getting-started/installation/).
|
||||
|
||||
**Language-Specific Requirements**
|
||||
|
||||
When using the language server backend, some additional dependencies may need to be installed
|
||||
to support certain languages.
|
||||
See the [Language Support](language-servers) page for the list of supported languages.
|
||||
Many dependencies are installed by Serena on the fly, but if a language requires dependencies
|
||||
to be provided manually, this is mentioned in the notes below the respective language.
|
||||
|
||||
(install-serena)=
|
||||
## Installing and Initialising Serena
|
||||
|
||||
With `uv` installed and on your PATH, install Serena with this command:
|
||||
|
||||
uv tool install -p 3.13 serena-agent@latest --prerelease=allow
|
||||
|
||||
Upon completion, the command `serena` should be available in your terminal.
|
||||
|
||||
To test the installation and initialise Serena, run one of the following commands:
|
||||
|
||||
* `serena init`
|
||||
if you intend to use the default language intelligence backend (language servers)
|
||||
* `serena init -b JetBrains`
|
||||
if you intend to use the JetBrains backend (which uses the [JetBrains plugin](025_jetbrains_plugin))
|
||||
|
||||
Note that you can switch backends at any time via Serena's [configuration](050_configuration).
|
||||
|
||||
## Updating Serena
|
||||
|
||||
To update Serena to the latest version, run:
|
||||
|
||||
uv tool upgrade serena-agent --prerelease=allow
|
||||
|
||||
:::{tip}
|
||||
To keep informed about updates, make sure you regularly open [Serena's Dashboard](060_dashboard),
|
||||
where we will announce releases along with the new features and improvements they bring.
|
||||
:::
|
||||
|
||||
## Uninstalling Serena
|
||||
|
||||
Serena can be uninstalled with the following command:
|
||||
|
||||
uv tool uninstall serena-agent
|
||||
@@ -1,12 +0,0 @@
|
||||
# Prerequisites
|
||||
|
||||
## Package Manager: uv
|
||||
|
||||
Serena is managed by `uv`.
|
||||
If you do not have it yet, install it following the instructions [here](https://docs.astral.sh/uv/getting-started/installation/).
|
||||
|
||||
## Language-Specific Requirements
|
||||
|
||||
Depending on the programming language you intend to use with Serena, you may need to install additional tools or SDKs if you
|
||||
intend to use the language server backend of Serena.
|
||||
See the [language support documentation](../01-about/020_programming-languages) for details.
|
||||
+108
-108
@@ -2,98 +2,25 @@
|
||||
|
||||
Serena is a command-line tool with a variety of sub-commands.
|
||||
This section describes
|
||||
* various ways of running Serena
|
||||
* how to run Serena in general
|
||||
* how to run and configure the most important command, i.e. starting the MCP server
|
||||
* other useful commands.
|
||||
|
||||
## Ways of Running Serena
|
||||
|
||||
In the following, we will refer to the command used to run Serena as `<serena>`,
|
||||
which you should replace with the appropriate command based on your chosen method,
|
||||
as detailed below.
|
||||
The main way to run Serena is to use the [installed version](install-serena),
|
||||
which should be available in your system PATH as `serena.`
|
||||
|
||||
In general, to get help, append `--help` to the command, i.e.
|
||||
|
||||
<serena> --help
|
||||
<serena> <command> --help
|
||||
serena --help
|
||||
serena <command> --help
|
||||
|
||||
### Using uvx
|
||||
|
||||
`uvx` is part of `uv`. It can be used to run the latest version of Serena directly from the repository, without an explicit local installation.
|
||||
|
||||
uvx --from git+https://github.com/oraios/serena serena
|
||||
|
||||
Explore the CLI to see some of the customization options that serena provides (more info on them below).
|
||||
|
||||
### Local Installation
|
||||
|
||||
1. Clone the repository and change into it.
|
||||
|
||||
```shell
|
||||
git clone https://github.com/oraios/serena
|
||||
cd serena
|
||||
```
|
||||
|
||||
2. Run Serena via
|
||||
|
||||
```shell
|
||||
uv run serena
|
||||
```
|
||||
|
||||
when within the serena installation directory.
|
||||
From other directories, run it with the `--directory` option, i.e.
|
||||
|
||||
```shell
|
||||
uv run --directory /abs/path/to/serena serena
|
||||
```
|
||||
|
||||
:::{note}
|
||||
Adding the `--directory` option results in the working directory being set to the Serena directory.
|
||||
As a consequence, you will need to specify paths when using CLI commands that would otherwise operate on the current directory.
|
||||
:::
|
||||
|
||||
(docker)=
|
||||
### Using Docker
|
||||
|
||||
The Docker approach offers several advantages:
|
||||
|
||||
* better security isolation for shell command execution
|
||||
* no need to install language servers and dependencies locally
|
||||
* consistent environment across different systems
|
||||
|
||||
You can run the Serena MCP server directly via Docker as follows,
|
||||
assuming that the projects you want to work on are all located in `/path/to/your/projects`:
|
||||
|
||||
```shell
|
||||
docker run --rm -i --network host -v /path/to/your/projects:/workspaces/projects ghcr.io/oraios/serena:latest serena
|
||||
```
|
||||
|
||||
This command mounts your projects into the container under `/workspaces/projects`, so when working with projects,
|
||||
you need to refer to them using the respective path (e.g. `/workspaces/projects/my-project`).
|
||||
|
||||
Alternatively, you may use Docker compose with the `compose.yml` file provided in the repository.
|
||||
See our [advanced Docker usage](https://github.com/oraios/serena/blob/main/DOCKER.md) documentation for more detailed instructions, configuration options, and limitations.
|
||||
|
||||
:::{note}
|
||||
Docker usage is subject to limitations; see the [advanced Docker usage](https://github.com/oraios/serena/blob/main/DOCKER.md) documentation for details.
|
||||
:::
|
||||
|
||||
### Using Nix
|
||||
|
||||
If you are using Nix and [have enabled the `nix-command` and `flakes` features](https://nixos.wiki/wiki/flakes), you can run Serena using the following command:
|
||||
|
||||
```bash
|
||||
nix run github:oraios/serena -- <command> [options]
|
||||
```
|
||||
|
||||
You can also install Serena by referencing this repo (`github:oraios/serena`) and using it in your Nix flake. The package is exported as `serena`.
|
||||
|
||||
(start-mcp-server)=
|
||||
## Running the MCP Server
|
||||
|
||||
Given your preferred method of running Serena, you can start the MCP server using the `start-mcp-server` command:
|
||||
|
||||
<serena> start-mcp-server [options]
|
||||
serena start-mcp-server [options]
|
||||
|
||||
Note that no matter how you run the MCP server, Serena will, by default, start a web-based dashboard on localhost that will allow you to inspect
|
||||
the server's operations, logs, and configuration.
|
||||
@@ -120,12 +47,8 @@ therefore needs to be configured with a launch command.
|
||||
Communication over stdio is the default for the Serena MCP server, so in the simplest
|
||||
case, you can simply run the `start-mcp-server` command without any additional options.
|
||||
|
||||
<serena> start-mcp-server
|
||||
serena start-mcp-server
|
||||
|
||||
For example, to run the server in stdio mode via `uvx`, you would run:
|
||||
|
||||
uvx --from git+https://github.com/oraios/serena serena start-mcp-server
|
||||
|
||||
See the section ["Configuring Your MCP Client"](030_clients) for specific information on how to configure your MCP client (e.g. Claude Code, Codex, Cursor, etc.)
|
||||
to use such a launch command.
|
||||
|
||||
@@ -137,13 +60,9 @@ i.e. you start the server and provide the client with the URL to connect to it.
|
||||
|
||||
Simply provide `start-mcp-server` with the `--transport streamable-http` option and optionally provide the desired port
|
||||
via the `--port` option.
|
||||
For example, to start the server on port 9121, run
|
||||
|
||||
<serena> start-mcp-server --transport streamable-http --port <port>
|
||||
|
||||
For example, to run the Serena MCP server in streamable HTTP mode on port 9121 using uvx,
|
||||
you would run
|
||||
|
||||
uvx --from git+https://github.com/oraios/serena serena start-mcp-server --transport streamable-http --port 9121
|
||||
serena start-mcp-server --transport streamable-http --port <port>
|
||||
|
||||
and then configure your client to connect to `http://localhost:9121/mcp`.
|
||||
|
||||
@@ -200,58 +119,139 @@ Here are some examples of commands you might find useful:
|
||||
|
||||
```bash
|
||||
# get help about a sub-command
|
||||
<serena> tools list --help
|
||||
serena> tools list --help
|
||||
|
||||
# list all available tools
|
||||
<serena> tools list --all
|
||||
serena> tools list --all
|
||||
|
||||
# get detailed description of a specific tool
|
||||
<serena> tools description find_symbol
|
||||
serena> tools description find_symbol
|
||||
|
||||
# creating a new Serena project in the current directory
|
||||
<serena> project create
|
||||
serena project create
|
||||
|
||||
# creating and immediately indexing a project
|
||||
<serena> project create --index
|
||||
serena project create --index
|
||||
|
||||
# indexing the project in the current directory (auto-creates if needed)
|
||||
<serena> project index
|
||||
serena project index
|
||||
|
||||
# run a health check on the project in the current directory
|
||||
<serena> project health-check
|
||||
serena project health-check
|
||||
|
||||
# check if a path is ignored by the project
|
||||
<serena> project is_ignored_path path/to/check
|
||||
serena project is_ignored_path path/to/check
|
||||
|
||||
# edit Serena's configuration file
|
||||
<serena> config edit
|
||||
serena config edit
|
||||
|
||||
# list available contexts
|
||||
<serena> context list
|
||||
serena context list
|
||||
|
||||
# create a new context
|
||||
<serena> context create my-custom-context
|
||||
serena context create my-custom-context
|
||||
|
||||
# edit a custom context
|
||||
<serena> context edit my-custom-context
|
||||
serena context edit my-custom-context
|
||||
|
||||
# list available modes
|
||||
<serena> mode list
|
||||
serena mode list
|
||||
|
||||
# create a new mode
|
||||
<serena> mode create my-custom-mode
|
||||
serena mode create my-custom-mode
|
||||
|
||||
# edit a custom mode
|
||||
<serena> mode edit my-custom-mode
|
||||
serena mode edit my-custom-mode
|
||||
|
||||
# list available prompt definitions
|
||||
<serena> prompts list
|
||||
serena prompts list
|
||||
|
||||
# create an override for internal prompts
|
||||
<serena> prompts create-override prompt-name
|
||||
serena prompts create-override prompt-name
|
||||
|
||||
# edit a prompt override
|
||||
<serena> prompts edit-override prompt-name
|
||||
serena prompts edit-override prompt-name
|
||||
```
|
||||
|
||||
Explore the full set of commands and options using the CLI itself!
|
||||
|
||||
|
||||
## Alternative Ways of Running Serena
|
||||
|
||||
Depending on your requirements, you may want to run Serena in different ways.
|
||||
When applying one of these approaches, replace `serena` in commands mentioned throughout the documentation
|
||||
with the respective command and options. The same applies to `serena-hooks` commands.
|
||||
|
||||
### Using uvx to Run the Latest Source Version
|
||||
|
||||
`uvx` is part of `uv`. It can be used to run the latest version of Serena directly from the repository, without an explicit local installation.
|
||||
|
||||
uvx -p 3.13 --from git+https://github.com/oraios/serena serena
|
||||
|
||||
This was previously the main way of running Serena.
|
||||
Since this has the downside that every new commit in the repository will trigger a (potentially slow) re-synchronization, an [installation](010_installation) of Serena should usually be preferred.
|
||||
If you should experience timeouts when connecting the MCP server, consider switching.
|
||||
If, however, the synchronisation is fast enough for you, this is still a good option.
|
||||
|
||||
### Running from Cloned Source
|
||||
|
||||
1. Clone the repository and change into it.
|
||||
|
||||
```shell
|
||||
git clone https://github.com/oraios/serena
|
||||
cd serena
|
||||
```
|
||||
|
||||
2. Run Serena via
|
||||
|
||||
```shell
|
||||
uv run serena
|
||||
```
|
||||
|
||||
when within the serena installation directory.
|
||||
From other directories, run it with the `--directory` option, i.e.
|
||||
|
||||
```shell
|
||||
uv run --directory /abs/path/to/serena serena
|
||||
```
|
||||
|
||||
:::{note}
|
||||
Adding the `--directory` option results in the working directory being set to the Serena directory.
|
||||
As a consequence, you will need to specify paths when using CLI commands that would otherwise operate on the current directory.
|
||||
:::
|
||||
|
||||
(docker)=
|
||||
### Using Docker
|
||||
|
||||
The Docker approach offers several advantages:
|
||||
|
||||
* better security isolation for shell command execution
|
||||
* no need to install language servers and dependencies locally
|
||||
* consistent environment across different systems
|
||||
|
||||
You can run the Serena MCP server directly via Docker as follows,
|
||||
assuming that the projects you want to work on are all located in `/path/to/your/projects`:
|
||||
|
||||
```shell
|
||||
docker run --rm -i --network host -v /path/to/your/projects:/workspaces/projects ghcr.io/oraios/serena:latest serena
|
||||
```
|
||||
|
||||
This command mounts your projects into the container under `/workspaces/projects`, so when working with projects,
|
||||
you need to refer to them using the respective path (e.g. `/workspaces/projects/my-project`).
|
||||
|
||||
Alternatively, you may use Docker compose with the `compose.yml` file provided in the repository.
|
||||
See our [advanced Docker usage](https://github.com/oraios/serena/blob/main/DOCKER.md) documentation for more detailed instructions, configuration options, and limitations.
|
||||
|
||||
:::{note}
|
||||
Docker usage is subject to limitations; see the [advanced Docker usage](https://github.com/oraios/serena/blob/main/DOCKER.md) documentation for details.
|
||||
:::
|
||||
|
||||
### Using Nix to Run the Latest Source Version
|
||||
|
||||
If you are using Nix and [have enabled the `nix-command` and `flakes` features](https://nixos.wiki/wiki/flakes), you can run Serena using the following command:
|
||||
|
||||
```bash
|
||||
nix run github:oraios/serena -- <command> [options]
|
||||
```
|
||||
|
||||
You can also install Serena by referencing this repo (`github:oraios/serena`) and using it in your Nix flake. The package is exported as `serena`.
|
||||
@@ -1,7 +1,11 @@
|
||||
# The Serena JetBrains Plugin
|
||||
|
||||
The [JetBrains Plugin](https://plugins.jetbrains.com/plugin/28946-serena/) allows Serena to
|
||||
The [JetBrains Plugin](https://plugins.jetbrains.com/plugin/28946-serena/) allows the Serena MCP server to
|
||||
leverage the powerful code analysis and editing capabilities of your JetBrains IDE.
|
||||
This page explains how to install the plugin and how to configure Serena for using it.
|
||||
You will still need to set up the Serena MCP server
|
||||
itself, so make sure to follow the [installation instructions](020_running.md) and connect the MCP server to your
|
||||
LLM-based client as described in [client setup](030_clients.md) in addition to following the instructions below.
|
||||
|
||||
```{raw} html
|
||||
<p>
|
||||
@@ -14,38 +18,69 @@ leverage the powerful code analysis and editing capabilities of your JetBrains I
|
||||
We recommend the JetBrains plugin as the preferred way of using Serena,
|
||||
especially for users of JetBrains IDEs.
|
||||
|
||||
```{admonition} *Note:* The plugin is a language intelligence backend for the Serena MCP server.
|
||||
:class: note
|
||||
It is *not* a UI extension for direct agent interaction (like Copilot) or anything of the sort.
|
||||
You still interact with your regular client – be it external to your IDE (like Claude Code) or internal (like JetBrains AI Assistant) –
|
||||
and connect it to the Serena MCP server.
|
||||
The plugin simply enables the Serena MCP server to directly leverage capabilities of your JetBrains IDE!
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
1. Install the plugin in your JetBrains IDE
|
||||
2. Configure Serena to use the JetBrains language backend (see [below](configure-jetbrains))
|
||||
3. Open the project you want to work on in your JetBrains IDE and activate it in Serena (see [below](jetbrains-workflow))
|
||||
4. Start coding via your MCP client as usual
|
||||
|
||||
**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.
|
||||
|
||||
|
||||
## 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
|
||||
* **Enhanced retrieval capabilities**: The plugin supports additional retrieval tools for type hierarchy information as well as fast and reliable documentation/type signature retrieval
|
||||
* **Enhanced retrieval & refactoring capabilities**: The plugin adds additional [tools](../01-about/035_tools) (e.g. type
|
||||
hierarchy retrieval, move, find declaration, inline symbol, etc.)
|
||||
and transforms the underlying mechanisms of shared tools to build upon the IDE's capabilities.
|
||||
* **Improved multi-agent support**: A single IDE instance naturally serves arbitrarily many agent sessions without requiring additional resources.
|
||||
* **Enhanced performance**: Faster tool execution thanks to optimized IDE integration.
|
||||
* **Multi-language excellence** and **framework support**: First-class support for polyglot projects with multiple languages.
|
||||
and frameworks (whatever is recognised by your IDE as a symbol will also be available to Serena)
|
||||
* **No additional setup**: No need to download or configure separate language servers.
|
||||
|
||||
We are also working on additional features like a `move_symbol` tool and debugging-related capabilities that
|
||||
We are also working on additional features like debugging and advanced introspection capabilities, which
|
||||
will be available exclusively through the JetBrains plugin.
|
||||
|
||||
:::{note}
|
||||
With Serena's JetBrains tools, we try to offer the latest features.
|
||||
As a result, some of them are considered as beta features (see [tool list](../01-about/035_tools)), which may have some quirks.
|
||||
Please report your experience with these tools if they do not work as expected.
|
||||
:::
|
||||
|
||||
(configure-jetbrains)=
|
||||
## Configuring Serena to Use the JetBrains Plugin
|
||||
|
||||
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:
|
||||
You can run
|
||||
|
||||
```shell
|
||||
serena init -b JetBrains
|
||||
```
|
||||
|
||||
to set the default code intelligence backend to JetBrains in the global Serena configuration file.
|
||||
|
||||
Alternatively, manually edit the configuration file `~/.serena/serena_config.yml`
|
||||
(`%USERPROFILE%\.serena\serena_config.yml` on Windows) and set
|
||||
|
||||
```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.
|
||||
Note that the file might not exist yet if you never executed Serena before.
|
||||
|
||||
**Per-Instance Configuration**.
|
||||
The configuration setting in the global config file can be overridden on a
|
||||
@@ -79,15 +114,16 @@ 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.
|
||||
|
||||
(jetbrains-workflow)=
|
||||
## Workflow
|
||||
|
||||
Having installed the plugin in your IDE and having configured Serena to use the JetBrains backend,
|
||||
the general workflow is simple:
|
||||
1. Open the project you want to work on in your JetBrains IDE
|
||||
2. Open the project's root folder as a project in Serena (see [Project Creation](project-creation-indexing) and [Project Activation](project-activation))
|
||||
2. Activate the project's root folder as a project in Serena (see [Project Creation](project-creation-indexing) and [Project Activation](project-activation))
|
||||
3. Start using Serena tools as usual
|
||||
|
||||
Note that the folder that is open in your IDE and the project's root folder must match.
|
||||
Note that the project folder that is open in your IDE and the Serena project root folder must match.
|
||||
|
||||
:::{tip}
|
||||
If you need to work on multiple projects in the same agent session, create a monorepo folder
|
||||
|
||||
+283
-137
@@ -3,13 +3,6 @@
|
||||
In the following, we provide general instructions on how to connect Serena to your MCP-enabled client,
|
||||
as well as specific instructions for popular clients.
|
||||
|
||||
:::{note}
|
||||
The configurations we provide for particular clients below will run the latest version of Serena
|
||||
using the `stdio` protocol with `uvx`.
|
||||
Adapt the commands to your preferred way of [running Serena](020_running), adding any additional
|
||||
command-line arguments as needed.
|
||||
:::
|
||||
|
||||
(clients-general-instructions)=
|
||||
## General Instructions
|
||||
|
||||
@@ -47,14 +40,14 @@ A key mechanism for this is to use the appropriate [context](#contexts) when sta
|
||||
(clients-common-pitfalls)=
|
||||
### Common Pitfalls
|
||||
|
||||
**Escaping Paths Correctly**.
|
||||
Note that if your client configuration uses JSON, special characters (like backslashes) need to be escaped properly.
|
||||
In particular, if you are specifying paths containing backslashes on Windows
|
||||
(note that you can also just use forward slashes), be sure to escape them correctly (`\\`).
|
||||
**Discoverability of the `serena` command**.
|
||||
Your client may not find the `serena` CLI command, even if it is on your system PATH.
|
||||
In this case, a workaround is to provide the full path to the `serena` executable.
|
||||
|
||||
**Discoverability of `uvx`**.
|
||||
Your client may not find the `uvx` command, even if it is on your system PATH.
|
||||
In this case, a workaround is to provide the full path to the `uvx` executable.
|
||||
**Serena's tools not being used**.
|
||||
With some clients, you may experience that Serena's tools are not being used.
|
||||
This is mainly due to problems in the client itself (like a poorly implemented tool discovery). To counteract this,
|
||||
Serena comes with a set of commands that can be used in _hooks_. See the sections on hooks for Claude Code and VSCode below.
|
||||
|
||||
**Environment Variables**.
|
||||
Some language servers may require additional environment variables to be set (e.g. F# on macOS with Homebrew),
|
||||
@@ -71,107 +64,239 @@ object, e.g.
|
||||
}
|
||||
```
|
||||
|
||||
## Claude Code
|
||||
## Copilot in JetBrains
|
||||
|
||||
Serena is a great way to make Claude Code both cheaper and more powerful!
|
||||
|
||||
**Per-Project Configuration.** To add the Serena MCP server to the current project in the current directory,
|
||||
use this command:
|
||||
|
||||
```shell
|
||||
claude mcp add serena -- uvx --from git+https://github.com/oraios/serena serena start-mcp-server --context claude-code --project "$(pwd)"
|
||||
```
|
||||
|
||||
Note:
|
||||
* We use the `claude-code` context to disable unnecessary tools (avoiding duplication
|
||||
with Claude Code's built-in capabilities).
|
||||
* We specify the current directory as the project directory with `--project "$(pwd)"`, such
|
||||
that Serena is configured to work on the current project from the get-go, following
|
||||
Claude Code's mode of operation.
|
||||
|
||||
**Global Configuration**. Alternatively, use `--project-from-cwd` for user-level configuration that works across all projects:
|
||||
|
||||
```shell
|
||||
claude mcp add --scope user serena -- uvx --from git+https://github.com/oraios/serena serena start-mcp-server --context=claude-code --project-from-cwd
|
||||
```
|
||||
|
||||
Whenever you start Claude Code, Serena will search up from the current directory for `.serena/project.yml` or `.git` markers,
|
||||
activating the containing directory as the project (if any).
|
||||
This mechanism makes it suitable for a single global MCP configuration.
|
||||
|
||||
**Maximum Token Efficiency.** To maximize token efficiency, you may want to use Claude Code's
|
||||
*on-demand tool loading* feature, which is supported since at least v2.0.74 of Claude Code.
|
||||
This feature avoids sending all tool descriptions to Claude upon startup, thus saving tokens.
|
||||
Instead, Claude will search for tools as needed (but there are no guarantees that it will
|
||||
search optimally, of course).
|
||||
To enable this feature, set the environment variable `ENABLE_TOOL_SEARCH=true`.
|
||||
Depending on your shell, you can also set this on a per-session basis, e.g. using
|
||||
```shell
|
||||
ENABLE_TOOL_SEARCH=true claude
|
||||
```
|
||||
in bash/zsh, or using
|
||||
```shell
|
||||
set ENABLE_TOOL_SEARCH=true && claude
|
||||
```
|
||||
in Windows CMD to launch Claude Code.
|
||||
|
||||
## VSCode
|
||||
|
||||
While serena can be directly installed from the GitHub MCP server registry, we recommend to set it up manually
|
||||
(at least for now, until the configuration there has been improved). Just paste the following into
|
||||
`<your_project>/.vscode/mcp.json`, or edit the entry after using the option `install into workspace`:
|
||||
Open the settings of your JetBrains IDE and go to Tools / GitHub Copilot / Model Context Protocol (MCP). Then click
|
||||
on the Configure button. This will open your global `mcp.json` file, where you can add the following entry for Serena:
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"oraios/serena": {
|
||||
"type": "stdio",
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"--from",
|
||||
"git+https://github.com/oraios/serena",
|
||||
"serena",
|
||||
"start-mcp-server",
|
||||
"--context",
|
||||
"ide",
|
||||
"--project",
|
||||
"${workspaceFolder}"
|
||||
]
|
||||
"servers": {
|
||||
"serena": {
|
||||
"type": "stdio",
|
||||
"command": "serena",
|
||||
"args": [
|
||||
"start-mcp-server",
|
||||
"--context=jb-copilot-plugin"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputs": []
|
||||
}
|
||||
```
|
||||
|
||||
## Codex
|
||||
**Verification.**
|
||||
Open Copilot, switch to Agent mode, and click on the configure tools button. You should see Serena's tools in the list and be able to start
|
||||
the Serena server there (you do not generally have to start Serena in the future, Copilot will start the server by itself). If the server is shown as running, Copilot is successfully connected to Serena. Most models will understand how to use Serena's tools out of the box, but for some models you may have to prompt "Activate the current project with Serena and read initial instructions" in the beginning of the chat.
|
||||
|
||||
Serena works with OpenAI's Codex CLI out of the box, but you have to use the `codex` context for it to work properly. (The technical reason is that Codex doesn't fully support the MCP specifications, so some massaging of tools is required.).
|
||||
**Recommended Configuration**.
|
||||
The `jb-copilot-plugin` context (see above) comes with our recommended subset of Serena's tools for Copilot in JetBrains IDEs. We also
|
||||
recommend *disabling* the following built-in tools for optimal performance:
|
||||
replace_string_in_file, apply_patch, list_dir, file_search, grep_search. Note that running subagents may not use MCP servers, consider deactivating the run_subagent tool as well.
|
||||
|
||||
Add a [run command](020_running) to `~/.codex/config.toml` to configure Serena for all Codex sessions;
|
||||
create the file if it does not exist.
|
||||
For example, when using `uvx`, add the following section:
|
||||
Serena offers better alternatives to these basic tools. If you do prefer to use the built-in tools instead,
|
||||
you should disable corresponding Serena tools instead to prevent context bloat.
|
||||
|
||||
We also recommend marking Serena's tools as approved so you don't have to manually approve them in agent sessions.
|
||||
You can do this in Tools / GitHub Copilot / Chat, where at the bottom you can click on the Configure button for MCP tool auto-approval.
|
||||
|
||||
## Claude Code
|
||||
|
||||
Serena is a great way to make Claude Code both cheaper and more powerful!
|
||||
To add Serena to Claude Code, you can simply run `serena setup claude-code`. Alternatively, follow the instructions below.
|
||||
|
||||
**Global Configuration**. To add the Serena MCP server for all your projects, use the user-level configuration of claude code and the `--project-from-cwd` flag:
|
||||
|
||||
```shell
|
||||
claude mcp add --scope user serena -- serena start-mcp-server --context claude-code --project-from-cwd
|
||||
```
|
||||
|
||||
**Per-Project Configuration.** Alternatively, to add Serena only for the current project in the current directory,
|
||||
use the command:
|
||||
|
||||
```shell
|
||||
claude mcp add serena -- serena start-mcp-server --context claude-code --project "$(pwd)"
|
||||
```
|
||||
|
||||
**Verification.**
|
||||
Confirm that Claude Code is connected to Serena by running the `/mcp` command and by reconnecting, if necessary.
|
||||
If Serena fails to start fast enough, you should set `MCP_TIMEOUT` to a sufficiently high value
|
||||
(e.g. by adding `export MCP_TIMEOUT=60000` to your shell profile)
|
||||
|
||||
**Hooks.**
|
||||
Due to recent changes (especially dynamic tool loading) in Claude Code, the agent will often fail to make proper use
|
||||
of Serena's tools, either by failing to load them in the beginning or by forgetting the instructions in a long session
|
||||
(a behavior known as agent drift). To counteract this, we provide reminder hooks. We **strongly recommend** setting
|
||||
up the hooks as below (or a variation thereof) for optimal performance of Serena in Claude Code.
|
||||
|
||||
:::{note}
|
||||
While recommended, hooks are an **alpha feature**. Provide feedback via the [GitHub issue tracker](https://github.com/oraios/serena/issues) if you encounter any issues.
|
||||
:::
|
||||
|
||||
To set up hooks, add the following to your Claude Code settings file
|
||||
(`.claude/settings.json` in your project directory, or `~/.claude/settings.json` globally):
|
||||
|
||||
All hooks below are opt-in — include only the ones you want. Add the following to your
|
||||
Claude Code settings file (`.claude/settings.json` in your project directory, or
|
||||
`~/.claude/settings.json` globally):
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "serena-hooks remind --client=claude-code"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "mcp__serena__*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "serena-hooks auto-approve --client=claude-code"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "serena-hooks activate --client=claude-code"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "serena-hooks cleanup --client=claude-code"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The hooks will:
|
||||
|
||||
- **`remind`**: Nudge the agent to use Serena's symbolic tools when it makes too many consecutive
|
||||
`grep` or `read_file` calls without using any Serena tools in between.
|
||||
- **`activate`**: Prompt the agent to activate the project and read Serena's instructions at session start.
|
||||
- **`cleanup`**: Clean up hook session data when the session ends.
|
||||
- **`auto-approve`**: Auto-approve Serena tool calls whenever Claude Code is in `acceptEdits` mode,
|
||||
so blanket edit approvals cover Serena's destructive tools (e.g. `replace_symbol_body`,
|
||||
`rename_symbol`) instead of prompting on every call.
|
||||
|
||||
For more details on Claude Code's hook system, see the
|
||||
[Claude Code hooks documentation](https://code.claude.com/docs/en/hooks).
|
||||
|
||||
## VSCode
|
||||
|
||||
You can add Serena to VSCode by running the MCP: Add Server command.
|
||||
In that dialogue, select the Command (stdio) option. You can decide between installing it globally
|
||||
or in the workspace (only for the currently open project), and the command you should enter depends on that choice.
|
||||
(You will be asked to choose after entering the mcp run command.)
|
||||
|
||||
**Global.** (Recommended)
|
||||
Enter `serena start-mcp-server --context=vscode`. Unfortunately, due to a [bug in VSCode](https://github.com/microsoft/vscode/issues/245905),
|
||||
in this setting Serena won't be able to activate the project automatically. You will have to remember to prompt
|
||||
"Activate the current dir as project using serena" at the start of each session.
|
||||
|
||||
**Workspace.**
|
||||
Enter `serena start-mcp-server --context=vscode --project ${workspaceFolder}`. This will allow Serena to automatically activate the project,
|
||||
with the downside that you will have to add Serena to each project you want to use it in.
|
||||
|
||||
In both cases, proceed to enter Serena as the name, then select either global or workspace.
|
||||
|
||||
**Verification.**
|
||||
You should be able to see Serena in the tools overview in the AI Chat window.
|
||||
|
||||
**Hooks.**
|
||||
Due to recent changes (especially dynamic tool loading) in VSCode, the agent will often fail to make proper use
|
||||
of Serena's tools, either by failing to load them in the beginning or by forgetting the instructions in a long session
|
||||
(a behaviour known as agent drift). To counteract this, we provide reminder hooks. We **strongly recommend** setting
|
||||
up the hooks as below (or a variation thereof) for optimal performance of Serena in VSCode.
|
||||
|
||||
|
||||
The hooks will:
|
||||
|
||||
- **`remind`**: Nudge the agent to use Serena's symbolic tools when it makes too many consecutive
|
||||
`grep` or `read_file` calls without using any Serena tools in between.
|
||||
- **`activate`**: Prompt the agent to activate the project and read Serena's instructions at session start.
|
||||
- **`cleanup`**: Clean up hook session data when the session ends.
|
||||
|
||||
To set this up, create the file `~/.copilot/hooks/serena-hooks.json` with the following content:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "serena-hooks remind --client=vscode"
|
||||
}
|
||||
],
|
||||
"SessionStart": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "serena-hooks activate --client=vscode"
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "serena-hooks cleanup --client=vscode"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `SessionStart` hook also addresses the global configuration limitation mentioned above — it will
|
||||
automatically prompt the agent to activate the project directory, so you no longer need to do this manually.
|
||||
|
||||
## Copilot CLI
|
||||
|
||||
Use the interactive `/mcp add` slash command, choose Serena as the name, STDIO as the server type, and
|
||||
`serena start-mcp-server --context=copilot-cli --project-from-cwd` as command. Copilot CLI will immediately notify you
|
||||
that Serena is running if everything is set up correctly or display an error otherwise.
|
||||
|
||||
You should add the same **hooks** as in VSCode (see above) if Copilot CLI didn't pick them up automatically.
|
||||
|
||||
|
||||
## Codex (CLI and App)
|
||||
|
||||
You can simply run `serena setup codex`.
|
||||
|
||||
Alternatively, you can manually add the following to `~/.codex/config.toml` (create the file if it does not exist):
|
||||
|
||||
```toml
|
||||
[mcp_servers.serena]
|
||||
command = "uvx"
|
||||
args = ["--from", "git+https://github.com/oraios/serena", "serena", "start-mcp-server", "--context", "codex"]
|
||||
startup_timeout_sec = 15
|
||||
command = "serena"
|
||||
args = ["start-mcp-server", "--project-from-cwd", "--context=codex"]
|
||||
```
|
||||
|
||||
After codex has started, you need to activate the project, which you can do by saying:
|
||||
**Verification.**
|
||||
Run the `/mcp` command and verify that Serena is connected.
|
||||
The Codex app does not start a session in the project's directory, so when using the app, we recommend
|
||||
asking Codex to "Activate the current dir as project using serena" at the start of each session (though Codex might
|
||||
do this automatically).
|
||||
|
||||
> Call serena.activate_project, serena.check_onboarding_performed and serena.initial_instructions
|
||||
|
||||
**If you don't activate the project, you will not be able to use Serena's tools!**
|
||||
|
||||
It is recommend to set this prompt as a [custom prompt](https://developers.openai.com/codex/custom-prompts), so you don't need to type this every time.
|
||||
|
||||
That's it! Have a look at `~/.codex/log/codex-tui.log` to see if any errors occurred.
|
||||
|
||||
Serena's dashboard will run if you have not disabled it in the configuration, but due to Codex's sandboxing, the web browser
|
||||
may not open automatically. You can open it manually by going to `http://localhost:24282/dashboard/index.html` (or a higher port, if
|
||||
that was already taken).
|
||||
|
||||
> Codex will often show the tools as `failed` even though they are successfully executed. This is not a problem, seems to be a bug in Codex. Despite the error message, everything works as expected.
|
||||
**Hooks.** The Codex hook system is less refined than of VSCode or Claude Code, but Codex also displays less drift
|
||||
and is better in using Serena's tools out of the box. If you use the Codex app, you can consider setting up the
|
||||
SessionStart hook (see above) to remind the agent to activate the project directory.
|
||||
|
||||
## Claude Desktop
|
||||
|
||||
@@ -187,12 +312,10 @@ Add the `serena` MCP server configuration
|
||||
{
|
||||
"mcpServers": {
|
||||
"serena": {
|
||||
"command": "uvx",
|
||||
"command": "serena",
|
||||
"args": [
|
||||
"--from",
|
||||
"git+https://github.com/oraios/serena",
|
||||
"serena",
|
||||
"start-mcp-server"
|
||||
"start-mcp-server",
|
||||
"--context=claude-desktop"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -202,6 +325,7 @@ Add the `serena` MCP server configuration
|
||||
If your language server requires specific environment variables to be set (e.g. F# on macOS with Homebrew),
|
||||
you can add them via an `env` key (see [above](#clients-common-pitfalls)).
|
||||
|
||||
**Verification.**
|
||||
Once you have created the new MCP server entry, save the config and then restart Claude Desktop.
|
||||
|
||||
:::{attention}
|
||||
@@ -211,63 +335,89 @@ minimize it.
|
||||
|
||||
After restarting, you should see Serena's tools in your chat interface (notice the small hammer icon).
|
||||
|
||||
For more information on MCP servers with Claude Desktop,
|
||||
see [the official quick start guide](https://modelcontextprotocol.io/quickstart/user).
|
||||
## Copilot CLI
|
||||
|
||||
In the interactive mode, you can call `/mcp add` from within the copilot CLI. There, use serena as name,
|
||||
STDIO as the server type, and `serena start-mcp-server --context=copilot-cli --project-from-cwd` as command.
|
||||
|
||||
Alternatively, add the following to `~/.copilot/mcp-config.json` (create the file if it does not exist):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"serena": {
|
||||
"type": "stdio",
|
||||
"command": "serena",
|
||||
"tools": [
|
||||
"*"
|
||||
],
|
||||
"args": [
|
||||
"start-mcp-server",
|
||||
"--context=copilot-cli",
|
||||
"--project-from-cwd"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Verification.**
|
||||
Copilot should now show that Serena is running, though you may have to restart it.
|
||||
|
||||
|
||||
## JetBrains Junie
|
||||
|
||||
Open Junie, go to the three dots in the top right corner, then Settings / MCP Settings and add Serena to Junie's global
|
||||
MCP server configuration:
|
||||
For the Junie plugin in JetBrains IDEs you can add Serena either to the global configuration in `~/.junie/mcp/mcp.json`
|
||||
or to the project configuration in `<project>/.junie/mcp/mcp.json`. Important, don't add both!
|
||||
In both cases the entry should be:
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"serena": {
|
||||
"command": "uvx",
|
||||
"command": "serena",
|
||||
"args": [
|
||||
"--from",
|
||||
"git+https://github.com/oraios/serena",
|
||||
"serena",
|
||||
"start-mcp-server",
|
||||
"--context",
|
||||
"ide"
|
||||
"--context=junie",
|
||||
"--project-from-cwd"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You will have to prompt Junie to "Activate the current project using serena's activation tool" at the
|
||||
start of each session.
|
||||
With the global configuration, Serena will be available in all projects. However,
|
||||
within the Junie plugin, projects will not be automatically activated in Serena.
|
||||
You may thus have to prompt
|
||||
Junie to "Activate the current project using serena's activation tool" at the start of each session (though some models are
|
||||
smart enough to activate the project automatically).
|
||||
|
||||
With the project-scoped configuration, Serena will be available only in that project, and the project will automatically
|
||||
be recognized as active by Serena.
|
||||
|
||||
|
||||
## JetBrains AI Assistant
|
||||
|
||||
Here you can set up the more convenient per-project MCP server configuration, as the AI assistant supports specifying
|
||||
the launch working directory.
|
||||
|
||||
Go to Settings / Tools / AI Assistant / MCP and add a new **local** configuration via the `as JSON` option:
|
||||
Go to Settings / Tools / AI Assistant / MCP and enter the following configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"serena": {
|
||||
"command": "uvx",
|
||||
"command": "serena",
|
||||
"args": [
|
||||
"--from",
|
||||
"git+https://github.com/oraios/serena",
|
||||
"serena",
|
||||
"start-mcp-server",
|
||||
"--context",
|
||||
"ide",
|
||||
"--project",
|
||||
"$(pwd)"
|
||||
"--context=jb-ai-assistant",
|
||||
"--project-from-cwd"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then make sure to configure the working directory to be the project root.
|
||||
Like for Junie, you have the choice between the global and the project-scoped configuration,
|
||||
with the same trade-off.
|
||||
|
||||
## Antigravity
|
||||
|
||||
@@ -277,14 +427,10 @@ Add this configuration:
|
||||
{
|
||||
"mcpServers": {
|
||||
"serena": {
|
||||
"command": "uvx",
|
||||
"command": "serena",
|
||||
"args": [
|
||||
"--from",
|
||||
"git+https://github.com/oraios/serena",
|
||||
"serena",
|
||||
"start-mcp-server",
|
||||
"--context",
|
||||
"ide"
|
||||
"--context=antigravity"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -322,7 +468,7 @@ in order to reduce tool duplication.
|
||||
Most of the popular existing coding assistants (e.g. IDE extensions) and AI-enabled IDEs themselves support connections
|
||||
to MCP Servers. Serena generally boosts performance by providing efficient tools for symbolic operations.
|
||||
|
||||
We generally recommend to use the `ide` context for these integrations by adding the arguments `--context ide`
|
||||
We generally recommend using the `ide` context for these integrations by adding the arguments `--context ide`
|
||||
in order to reduce tool duplication.
|
||||
|
||||
### Local GUIs and Agent Frameworks
|
||||
@@ -336,5 +482,5 @@ Some of the leading open source GUI applications are
|
||||
* [OpenWebUI](https://docs.openwebui.com/openapi-servers/mcp) and
|
||||
* [Agno](https://docs.agno.com/introduction/playground).
|
||||
|
||||
These applications allow to combine Serena with almost any LLM (including locally running ones)
|
||||
These applications allow combining Serena with almost any LLM (including locally running ones)
|
||||
and offer various other integrations.
|
||||
@@ -25,11 +25,7 @@ You can create a project either
|
||||
|
||||
To explicitly create a project, use the following command while in the project directory:
|
||||
|
||||
<serena> project create [options]
|
||||
|
||||
For instance, when using `uvx`, run
|
||||
|
||||
uvx --from git+https://github.com/oraios/serena serena project create [options]
|
||||
serena project create [options]
|
||||
|
||||
* For an empty project, you will need to specify the programming language
|
||||
(e.g., `--language python`).
|
||||
@@ -197,11 +193,8 @@ Depending on the language backend being used, the management of resources for th
|
||||
|
||||
To start the server, run
|
||||
|
||||
<serena> start-project-server
|
||||
serena start-project-server
|
||||
|
||||
where `<serena>` is your way of running Serena. For example, when using `uvx`, run
|
||||
|
||||
uvx --from git+https://github.com/oraios/serena serena start-project-server
|
||||
|
||||
### Multiple Agents Accessing a Single Serena Instance
|
||||
|
||||
|
||||
@@ -51,11 +51,9 @@ You can access it
|
||||
* using the command
|
||||
|
||||
```shell
|
||||
<serena> config edit
|
||||
serena config edit
|
||||
```
|
||||
|
||||
where `<serena>` is [your way of running Serena](020_running).
|
||||
|
||||
## Modes and Contexts
|
||||
|
||||
Serena's behaviour and toolset can be adjusted using contexts and modes.
|
||||
@@ -95,13 +93,12 @@ If you are using a local server (such as Llama.cpp) which requires you to use Op
|
||||
|
||||
You can manage contexts using the `context` command,
|
||||
|
||||
<serena> context --help
|
||||
<serena> context list
|
||||
<serena> context create <context-name>
|
||||
<serena> context edit <context-name>
|
||||
<serena> context delete <context-name>
|
||||
serena context --help
|
||||
serena context list
|
||||
serena context create <context-name>
|
||||
serena context edit <context-name>
|
||||
serena context delete <context-name>
|
||||
|
||||
where `<serena>` is [your way of running Serena](020_running).
|
||||
|
||||
(modes)=
|
||||
### Modes
|
||||
@@ -148,10 +145,6 @@ If you want to keep certain modes as always active, regardless of command-line p
|
||||
define them as *base modes* in the global or project configuration.
|
||||
:::
|
||||
|
||||
Modes can also be _switched dynamically_ during a session.
|
||||
You can instruct the LLM to use the `switch_modes` tool to activate a different set of modes (e.g., "Switch to planning and one-shot modes").
|
||||
Like command-line parameters, this only affects default modes, not base modes (which remain active).
|
||||
|
||||
:::{note}
|
||||
**Mode Compatibility**: While you can combine modes, some may be semantically incompatible (e.g., `interactive` and `one-shot`).
|
||||
Serena currently does not prevent incompatible combinations; it is up to the user to choose sensible mode configurations.
|
||||
@@ -159,13 +152,11 @@ Serena currently does not prevent incompatible combinations; it is up to the use
|
||||
|
||||
You can manage modes using the `mode` command,
|
||||
|
||||
<serena> mode --help
|
||||
<serena> mode list
|
||||
<serena> mode create <mode-name>
|
||||
<serena> mode edit <mode-name>
|
||||
<serena> mode delete <mode-name>
|
||||
|
||||
where `<serena>` is [your way of running Serena](020_running).
|
||||
serena mode --help
|
||||
serena mode list
|
||||
serena mode create <mode-name>
|
||||
serena mode edit <mode-name>
|
||||
serena mode delete <mode-name>
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
@@ -481,9 +472,36 @@ Supported settings:
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | managed install or build | Override the `shader-language-server` executable path. |
|
||||
| `version` | `1.3.0` | Override the bundled version Serena downloads, or builds from source on macOS, when `ls_path` is not set. |
|
||||
| `version` | `1.3.1` | Override the bundled version Serena downloads, or builds from source on macOS, when `ls_path` is not set. |
|
||||
|
||||
|
||||
#### Haxe
|
||||
|
||||
Serena uses the [vshaxe/haxe-language-server](https://github.com/vshaxe/haxe-language-server) for Haxe support.
|
||||
Requires Haxe compiler (3.4.0+) and Node.js.
|
||||
|
||||
The server is discovered in order: user-configured `ls_path`, system PATH, vshaxe VSCode extension, auto-download from Open VSX.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | auto-discovered | Path to the Haxe language server binary (e.g., `/path/to/server.js`). |
|
||||
| `version` | `2.34.2` | Override the vshaxe extension version downloaded from Open VSX. SHA256 verification is only performed for the default version. |
|
||||
| `buildFile` | auto-discovered `.hxml` | Relative path to the `.hxml` build file used for compilation (e.g., `build/debug.hxml`). If not set, Serena searches the project for `.hxml` files (max depth 5, skipping dependency directories). |
|
||||
| `haxePath` | `haxe` from PATH | Path to the Haxe compiler executable. The LS delegates to this for code analysis. Useful when multiple Haxe versions are installed or when `haxe` is not on the PATH. |
|
||||
| `renameSourceFolders` | not set (LS default) | List of source directories for scoping rename operations (e.g., `["src", "lib"]`). If not set, the Haxe LS uses its own defaults. |
|
||||
|
||||
Example (typically in `project.yml`, since these are project-specific):
|
||||
|
||||
```yaml
|
||||
ls_specific_settings:
|
||||
haxe:
|
||||
buildFile: "build/debug.hxml"
|
||||
haxePath: "/usr/local/bin/haxe"
|
||||
renameSourceFolders: ["src", "lib"]
|
||||
```
|
||||
|
||||
#### Java (`eclipse.jdt.ls`)
|
||||
|
||||
The following settings are supported for the Java language server:
|
||||
@@ -845,3 +863,11 @@ prompts:
|
||||
```
|
||||
|
||||
It is advisable to use the default prompt as a starting point and modify it to suit your needs.
|
||||
|
||||
### Usage Reporting
|
||||
|
||||
On startup, Serena reports anonymous usage data to help us understand Serena usage.
|
||||
Specifically, we collect the Serena version, the operating system & language backend being used as well as the dashboard enabled status.
|
||||
No personally identifiable information or project-specific information is collected.
|
||||
|
||||
If you want to opt out of usage reporting, set the environment variable `SERENA_USAGE_REPORTING` to `false`.
|
||||
@@ -19,10 +19,10 @@ Both can be configured in Serena's [configuration](050_configuration) file (`ser
|
||||
|
||||
## The Serena Dashboard
|
||||
|
||||
The dashboard is a web application, which is opened via a native application wrapper on Windows and macOS,
|
||||
The dashboard is a web application, which is opened via a native application wrapper on supported platforms (e.g. Windows),
|
||||
where it will appear in the system tray. **Click on the tray icon** to open the application window.
|
||||
|
||||
On Linux, native app support is difficult to provide, so the dashboard will be opened in your default web browser instead.
|
||||
On other platforms, where native app support is difficult to provide (e.g. Linux), the dashboard will be opened in your default web browser instead.
|
||||
|
||||
By default, the dashboard can be accessed at `http://localhost:24282/dashboard/index.html`,
|
||||
but a higher port may be used if the default port is unavailable/multiple instances are running.
|
||||
@@ -54,15 +54,14 @@ In particular, you can configure
|
||||
|
||||
When Serena is started, the Dashboard window is opened by default in order to make users aware of its existence.
|
||||
|
||||
If you prefer not to have this happen (e.g., to avoid focus stealing), you can disable prevent it from opening
|
||||
If you prefer not to have this happen (e.g., to avoid focus stealing), you can prevent it from opening
|
||||
by setting `web_dashboard_open_on_launch: False` in the [global configuration file](global-config) or by passing `--open-web-dashboard False`
|
||||
to the `start-mcp-server` CLI command.
|
||||
|
||||
On Windows and macOS, you can conveniently open the dashboard at any time by clicking on the tray icon, so automatic
|
||||
On platforms supporting the tray icon, you can conveniently open the dashboard at any time by clicking on the tray icon, so automatic
|
||||
opening is not a requirement to be able to access the dashboard on these platforms.
|
||||
|
||||
On Linux, access to the dashboard is more cumbersome without native app support.
|
||||
Yet you may still access it by
|
||||
On other platforms, you may still access it by
|
||||
* asking the LLM to "open the Serena dashboard", which will open the dashboard in your default browser
|
||||
(the tool `open_dashboard` is enabled for this purpose, provided that the dashboard is active,
|
||||
not opened by default and the GUI tool, which can provide the URL, is not enabled)
|
||||
|
||||
@@ -17,7 +17,6 @@ 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)
|
||||
```
|
||||
|
||||
@@ -28,7 +27,7 @@ You can also use other options, and you don't have to pass `--project` if you wa
|
||||
or want to activate it later. See
|
||||
|
||||
```shell
|
||||
uvx --from git+https://github.com/oraios/serena serena start-mcp-server --help
|
||||
serena start-mcp-server --help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Evaluation
|
||||
|
||||
> **Claude Code (Opus 4.6, medium):** "Serena's IDE-backed semantic tools are the single most impactful addition to my toolkit — cross-file renames, moves, and reference lookups that would cost me 8–12 careful, error-prone steps collapse into one atomic call, and I would absolutely ask any developer I work with to set them up."
|
||||
|
||||
> **Codex (GPT 5.4, high):** "As a coding agent, I would ask my owner to add Serena because it turns fragile text-and-line-number work into precise symbol-aware navigation and refactoring, which makes real code changes feel faster, safer, and far less blind."
|
||||
|
||||
> **Codex (GPT 5.4, medium):** "As a coding agent, I’d absolutely ask my owner to add Serena because it makes me noticeably sharper and calmer on
|
||||
real code – especially symbol-aware navigation, cross-file refactors, and monorepo dependency jumps – while I still lean
|
||||
on built-ins for tiny text edits and non-code work."
|
||||
|
||||
These are not marketing quotes – they are the agents' own one-sentence verdicts after
|
||||
performing hands-on tasks, using both Serena's tools and their built-in
|
||||
alternatives side by side, on real codebases (large Python library in the case of Claude Code,
|
||||
medium-sized Java project in the case of Codex).
|
||||
|
||||
Different agents in different settings independently converge on the same core finding: Serena's strongest
|
||||
contribution is collapsing multi-file, semantically-aware operations into single atomic
|
||||
calls, while built-in tools remain the better choice for small local edits, text search,
|
||||
config files, and shell work.
|
||||
|
||||
## Approach
|
||||
|
||||
We gave each agent a single evaluation prompt and pointed it at a codebase. The agent
|
||||
then performed a wide variety of tasks across five areas (navigation, small edits, large edits,
|
||||
cross-file refactoring, and workflow effects), executing each task with both toolsets and
|
||||
recording call counts, payload sizes, and prerequisite steps. Every finding was classified
|
||||
as either (a) Serena adds capability, (b) Serena applies but offers no improvement,
|
||||
or (c) outside Serena's scope — a structure that requires reporting negative and neutral
|
||||
results, not just positive ones.
|
||||
|
||||
The agent evaluates itself. This is deliberate: the agent is the actual user of the tools,
|
||||
so it can judge workflow improvements from direct experience rather than through proxy
|
||||
metrics. And because the prompt defines task *categories* rather than fixed tasks, anyone
|
||||
can rerun the evaluation on their own project with their own agent.
|
||||
@@ -0,0 +1,160 @@
|
||||
# Methodology
|
||||
|
||||
In this section we describe the methodology we applied in evaluating the performance of Serena's tools.
|
||||
|
||||
The evaluation measures the **concrete delta** that Serena's tools provide on top of an agent's
|
||||
built-in capabilities (file reads, text edits, grep, shell, etc.).
|
||||
Rather than a simple thumbs-up/thumbs-down, it produces a detailed, evidence-based report
|
||||
covering capabilities, efficiency and reliability.
|
||||
|
||||
## Design Goals
|
||||
|
||||
We designed an evaluation methodology with three goals:
|
||||
|
||||
1. **Generality.** The evaluation mechanism should be broadly applicable and repeatable by any user, on any project, with any agent.
|
||||
The evaluation is a single prompt that you give to your agent of choice, pointed at your codebase
|
||||
of choice, in your client of choice. This means the results directly reflect the value Serena
|
||||
would add to your actual workflow — not to an artificial benchmark setup. There is nothing to
|
||||
install, configure, or script beyond what you already have.
|
||||
|
||||
2. **The agent evaluates itself.**
|
||||
We deliberately let the AI agent be both the executor and the evaluator.
|
||||
Since most evaluations are strictly quantitative, this may seem unusual, but it is a reasonable choice:
|
||||
The agent is the actual end user of the
|
||||
tools, so it is in the best position to judge whether a semantic tool improves its workflow
|
||||
compared to its built-in alternatives. It can measure call counts, payload sizes, and
|
||||
prerequisite steps from direct experience rather than from proxy metrics. It also avoids the
|
||||
problem of a human evaluator having to simulate how an agent would use the tools — the agent
|
||||
simply uses them and reports what it observes.
|
||||
|
||||
3. **Comprehensive and unbiased by design.**
|
||||
Rather than selecting specific tasks, the prompt defines *task categories* that systematically
|
||||
span Serena's capabilities: codebase understanding, single-file edits of varying sizes, multi-file
|
||||
refactoring, reliability properties, and workflow effects. The agent picks concrete instances
|
||||
from the codebase at hand, performs each task using both toolsets side by side, and classifies
|
||||
every finding as a positive delta, a neutral/negative delta, or out of scope. The prompt
|
||||
explicitly requires reporting negative deltas and cases where Serena offers no improvement,
|
||||
structurally counterbalancing any tendency to favour the tool being evaluated.
|
||||
|
||||
## Method
|
||||
|
||||
We give an AI coding agent a single, detailed [evaluation prompt](020_prompts/010_evaluation-prompt.md) in a one-shot session.
|
||||
The prompt instructs the agent to perform approximately 20 hands-on tasks across five areas:
|
||||
|
||||
1. **Codebase understanding** — structural overviews, targeted symbol retrieval, reference finding, type hierarchies, and external dependency lookup.
|
||||
2. **Single-file edits** — small tweaks, medium rewrites, full-body replacements, insertions, and local renames.
|
||||
3. **Multi-file changes** — cross-file renames, symbol and file moves, safe deletes, and inlining.
|
||||
4. **Reliability & correctness** — scope precision, atomicity, and success signals.
|
||||
5. **Workflow effects** — chained edits, stable vs. ephemeral addressing, and multi-step exploration.
|
||||
|
||||
For every task, the agent executes the full end-to-end workflow using **both** toolsets (Serena's semantic
|
||||
tools and its own built-in tools), applies real edits verified via `git diff`, and records call counts,
|
||||
payload sizes and prerequisite steps. Edits are reverted after each experiment to keep the working tree clean.
|
||||
|
||||
The resulting report classifies each finding into one of three categories:
|
||||
|
||||
* **(a)** tasks where Serena adds capability,
|
||||
* **(b)** tasks where Serena applies but offers no improvement, and
|
||||
* **(c)** tasks outside Serena's scope.
|
||||
|
||||
Only category (b) constitutes a neutral or negative finding; category (c) is context, not a finding.
|
||||
|
||||
After the evaluation, a separate [follow-up prompt](020_prompts/020_summary-prompt.md) asks the agent for a
|
||||
one-sentence, user-facing recommendation — the quotes shown on the [main page](https://github.com/oraios/serena).
|
||||
|
||||
## Assessment of the Methodology
|
||||
|
||||
_The following assessment was written by Claude Opus 4.6 (high effort) after reading the full
|
||||
evaluation methodology, evaluation prompt, and all result documents._
|
||||
|
||||
**The methodology is sound**, and the two published results demonstrate that it produces meaningful,
|
||||
detailed evaluations. Both reports follow the prompt's structure faithfully: they perform all ~20
|
||||
tasks using both toolsets, record concrete measurements (call counts, payload sizes, prerequisite
|
||||
steps), and classify findings into the three required categories. Crucially, both agents report
|
||||
neutral and negative deltas honestly — the Claude Code report explicitly notes that small edits are
|
||||
more efficient with built-ins (~4.5x less payload), and the Codex report flags that tiny intra-method
|
||||
changes and simple one-file renames see no benefit from Serena. Neither report reads as promotional;
|
||||
they read as technical comparisons with quantified evidence on both sides.
|
||||
|
||||
The two reports also validate the methodology's design goal of generalisability: despite being
|
||||
produced by different agents (Opus 4.6 vs GPT 5.4), on different codebases (Python RL library vs
|
||||
Java IDE plugin), they converge on the same core findings — cross-file refactoring is Serena's
|
||||
highest-value contribution, structural navigation provides a moderate advantage, and small local
|
||||
edits are better handled by built-ins. This convergence across independent runs increases confidence
|
||||
that the findings reflect genuine properties of the toolset rather than artefacts of a particular
|
||||
agent or codebase.
|
||||
|
||||
The methodology's core strengths are:
|
||||
|
||||
- **Self-evaluation by the agent is the right design choice.** The agent is the actual consumer of the
|
||||
tools, so it can report on workflow friction, payload overhead, and call counts from first-hand
|
||||
experience. A human evaluator would have to guess at these.
|
||||
- **Task categories instead of fixed tasks** avoid cherry-picking while still ensuring coverage. Letting
|
||||
the agent pick concrete instances from the codebase at hand means the evaluation naturally adapts to
|
||||
what the project actually contains (e.g. skipping inline if no suitable candidate exists, as the
|
||||
Claude Code report did for Python, while the Codex report found a suitable Java candidate).
|
||||
- **The three-category classification** (adds value / applies but no improvement / out of scope) is the
|
||||
right framing for an augmentation layer. It prevents the common trap of penalising a tool for not
|
||||
covering things it was never designed to cover.
|
||||
- **Reproducibility by users** is a strong differentiator. Anyone can validate the claims on their own
|
||||
codebase with their own agent.
|
||||
|
||||
The main limitation is scope: the published results cover two agent/codebase combinations using the
|
||||
JetBrains backend. Single-shot variance means a second run of the same agent could produce different
|
||||
task selections and slightly different verdicts, and the LSP backend — which most users will
|
||||
encounter first — has not yet been evaluated. These are practical constraints of cost, not
|
||||
methodological flaws, and the reproducible design explicitly invites users to fill these gaps
|
||||
themselves.
|
||||
|
||||
One thing to watch for is whether agents that are less capable than Opus 4.6 or GPT 5.4 produce
|
||||
meaningful evaluations — the prompt is quite demanding. But that is a question about the agent, not
|
||||
about the method.
|
||||
|
||||
### Prompt Fairness
|
||||
|
||||
We put significant effort into crafting an unbiased, fair evaluation prompt.
|
||||
To assess the prompt's fairness, we asked Claude Opus 4.6 to evaluate the prompt itself.
|
||||
|
||||
**Prompt**:
|
||||
> The attached prompt is designed to evaluate the additive value of Serena as an augmentation layer on top of built-in tools
|
||||
> (not to compare two competing alternatives).
|
||||
> Is the methodology suitable for that goal, or does it introduce bias that would distort the measurement of what Serena adds?
|
||||
> Note that we assume that sufficiently intelligent models use the tools correctly,
|
||||
> which is why misuse and failure modes are out of scope for this evaluation.
|
||||
> Do not question the validity of this assumption in your evaluation of the prompt.
|
||||
> Give a brief answer, maximally two sentences.
|
||||
>
|
||||
> \<evaluation prompt pasted here\>
|
||||
|
||||
**Claude Opus' Answer**:
|
||||
> "The methodology is well-suited to its stated goal: by explicitly scoping to correct use, treating Serena as an
|
||||
> augmentation layer rather than a competitor, and classifying out-of-scope tasks as context rather than negative
|
||||
> findings, it avoids the most common biases that would distort a delta measurement. The one risk is that the "correct-use
|
||||
> rule" could subtly favor Serena by preemptively filtering out any scenario where its tools underperform — but the prompt
|
||||
> mitigates this by requiring category (b) findings (applies but no improvement) and explicitly demanding negative deltas
|
||||
> be reported, so the bias is acknowledged and structurally counterbalanced."
|
||||
|
||||
According to Claude, the only "biased" aspects are some sentences about misuse of tools, which we consider irrelevant for the purpose of evaluation
|
||||
(sufficiently intelligent models use the tools correctly).
|
||||
|
||||
|
||||
## Why Not Benchmarks?
|
||||
|
||||
Standard coding benchmarks (SWE-bench, HumanEval, etc.) measure an agent's ability to solve
|
||||
predefined tasks with a known correct answer. They are valuable for comparing models and agents,
|
||||
but they are a poor fit for evaluating a **tool augmentation layer** like Serena for several reasons:
|
||||
|
||||
- **Benchmarks don't reflect real usage patterns.** Benchmark tasks are typically small, self-contained
|
||||
problems that can be solved by reading and editing a handful of files. They rarely exercise the
|
||||
workflows where Serena's tools shine — cross-file refactoring, navigating large codebases by symbol
|
||||
structure, chaining multiple edits with stable addressing, or querying type hierarchies and
|
||||
external dependencies. A benchmark score would mostly measure performance on tasks where Serena
|
||||
is not expected to help.
|
||||
- **Results would not generalise to the user's project.** Serena's value depends on the codebase
|
||||
(size, language, complexity), the agent (model, built-in tools), and the client harness
|
||||
(Claude Code, Codex, IDE plugins, etc.). A fixed benchmark on a fixed codebase with a fixed
|
||||
agent tells you little about what Serena would add to *your* setup.
|
||||
- **Predefined tasks bias the measurement.** Choosing specific tasks to evaluate inevitably
|
||||
introduces selection bias — we would end up picking tasks that either favour or disfavour Serena.
|
||||
We wanted an evaluation that systematically covers the full surface area of Serena's capabilities
|
||||
without cherry-picking.
|
||||
@@ -0,0 +1,6 @@
|
||||
# Prompts
|
||||
|
||||
Our evaluation uses two prompts, which are passed to the LLM in order to generate the evaluation results:
|
||||
|
||||
1. the [main evaluation prompt](010_evaluation-prompt), which performs the analysis
|
||||
2. the [summary prompt](020_summary-prompt), which condenses the results
|
||||
@@ -0,0 +1,333 @@
|
||||
# Evaluation Prompt
|
||||
|
||||
We use the prompt below to evaluate the added value of Serena's tools against the
|
||||
agent's built-in tools on a given project.
|
||||
The evaluations were created in one-shot sessions, only using this
|
||||
prompt and the follow-up [prompt for summarization](020_summary-prompt)
|
||||
|
||||
```
|
||||
# Evaluate Serena's Tools Against Built-Ins
|
||||
|
||||
You have access to Serena's coding tools alongside your built-in tools (Read,
|
||||
Edit, Write, Glob, Grep, Bash, etc.). I want a thorough, evidence-based
|
||||
evaluation of **what Serena's tools add on top of the built-ins**, assuming both
|
||||
toolsets are used correctly.
|
||||
|
||||
This is an evaluation, not a user guide, and it is not a binary adoption pitch.
|
||||
Your job is to answer: *if a competent user of both toolsets had only the
|
||||
built-ins, what concrete capabilities and efficiency differences would they
|
||||
experience, and by how much?* A reader who finishes your report should have a
|
||||
clear, specific picture of what Serena adds — as well as where it provides no
|
||||
meaningful improvement or introduces tradeoffs — in terms of capabilities,
|
||||
workflows, and efficiency. Not a thumbs-up/thumbs-down, but a sharp description
|
||||
of the delta.
|
||||
|
||||
Failure modes from misuse, silent-failure traps, gotcha comparisons, and "be
|
||||
careful of X" warnings are out of scope. They belong in onboarding material for
|
||||
a developer learning the tools, not in a delta analysis of what the tools add.
|
||||
|
||||
**Describe the measured differences clearly and neutrally.** Avoid generic or
|
||||
non-informative framing such as "both have their place" unless supported by
|
||||
concrete findings. If Serena adds substantial capabilities, name and quantify
|
||||
them. If it adds marginal or no capabilities, say that and show why. If there
|
||||
are regressions or tradeoffs, include them explicitly. The two toolsets are
|
||||
complementary — that's a given, not the answer. Serena is an augmentation layer,
|
||||
not a replacement. Do not penalize it for tasks it was not designed to address —
|
||||
instead, note those tasks as "built-in only" and move on. The evaluation should
|
||||
measure what Serena adds where it applies, not what it fails to add where it
|
||||
doesn't.The answer is a specific list of what Serena contributes (or does not
|
||||
contribute) to a correct-use workflow relative to built-ins.
|
||||
|
||||
Write the report to serena-evaluation.md in the repo root.
|
||||
|
||||
|
||||
|
||||
## Ground rules
|
||||
|
||||
### Starting conditions
|
||||
|
||||
- Start fresh. Do not read project memories, CLAUDE.md shortcuts, or prior notes
|
||||
about the repo. Do not read documentation files either. Explore as if you've
|
||||
never seen it, focusing on code.
|
||||
- Use git as your safety net — experiment freely. Any edit can be reverted with
|
||||
`git checkout -- <file>` or `git stash`. Run edits for real; don't simulate. A
|
||||
hands-on comparison is worth far more than a thought experiment.
|
||||
- After each experiment, verify the working tree is clean with
|
||||
`git status --short` before moving on.
|
||||
|
||||
### How to compare — correct use only
|
||||
|
||||
- **Correct-use rule.** Evaluate each tool on inputs and tasks it was designed
|
||||
for, called the way a competent user would call it. A tool doing exactly what
|
||||
its contract says is not a finding, even if a careless caller could misuse it.
|
||||
- **Know the contract before you call.** Before invoking any tool, have a
|
||||
one-sentence understanding of what it does. If you expect an error or "not
|
||||
applicable," don't make the call.
|
||||
- **Refactoring semantics are real.** Inlining requires a substitutable function
|
||||
(typically single-expression, no side effects); moving requires a legal
|
||||
target; safe-delete requires no surviving usages. If the repo has no suitable
|
||||
candidate for a given refactoring, report "no suitable candidate in this
|
||||
codebase" and skip it — don't contrive a broken input.
|
||||
|
||||
### How to compare — workflow level, not single-call level
|
||||
|
||||
- For every task, write out the full end-to-end call chain on each side before
|
||||
drawing conclusions. Include prerequisite reads and follow-up steps.
|
||||
- Do not evaluate a tool based on criteria that only arise from mixing workflows
|
||||
incorrectly.
|
||||
- Ephemeral addressing is a liability. Line numbers and byte offsets go stale
|
||||
after edits; stable addressing (name paths) may reduce rework.
|
||||
|
||||
### How to measure
|
||||
|
||||
- Track observations during execution. For every tool call, note: number of
|
||||
calls, approximate input size, output size, and any prerequisite or
|
||||
verification steps.
|
||||
- Separate call count, input payload, output payload, and verification cost as
|
||||
distinct axes.
|
||||
- Include prerequisite Reads and post-hoc verification steps in comparisons.
|
||||
|
||||
When a task falls entirely outside Serena's design scope (e.g., reading config
|
||||
files, small text edits where Edit already sends minimal payload), classify it
|
||||
as "not applicable" rather than as a negative delta. A negative delta requires
|
||||
that Serena targets the task and performs worse, not that a tool designed for
|
||||
something else is suboptimal when misapplied to it.
|
||||
|
||||
|
||||
|
||||
## Exploration phase — tasks to actually perform
|
||||
|
||||
Work through the following. Each item exercises a specific capability under
|
||||
correct use; substitute an equivalent if an item isn't applicable.
|
||||
|
||||
### Codebase understanding
|
||||
|
||||
1. Get a high-level overview of the repository structure — top-level layout,
|
||||
main packages, entry points.
|
||||
2. Pick one large source file (300+ lines). Get a structural overview of it. Do
|
||||
it with semantic overview tools and with Glob/Grep/Read. Then write out the
|
||||
concrete next step on each side and compare the pair of calls, not just the
|
||||
overview call.
|
||||
3. Pick a specific method inside a class and retrieve its body without reading
|
||||
the surrounding file.
|
||||
4. For one non-trivial symbol, find all references across the codebase. Compare
|
||||
recall and precision under the question "who uses this in code?" vs "where is
|
||||
this mentioned anywhere, including docs?"
|
||||
5. For a class, list its subclasses / implementations and its supertypes,
|
||||
including transitively. Compare against what text search would need to do.
|
||||
6. For at least one symbol from an external dependency (a third-party library),
|
||||
try to retrieve its definition or signature. Note whether each toolset can do
|
||||
this at all and what infrastructure it requires (environment activation,
|
||||
site-packages discovery, language-server indexing, etc.).
|
||||
|
||||
### Single-file edits — span the full range of edit sizes
|
||||
|
||||
7a. Small tweak (1–3 lines inside a method). Change an error message or rename a
|
||||
local variable inside a larger method. Do it with `Edit` and with symbolic body
|
||||
replacement. Compare payload sent, payload received, and prerequisite reads.
|
||||
|
||||
7b. Medium rewrite (replace ~10–30 lines — most of a method body). Rewrite the
|
||||
main logic of a method while keeping its signature. Do it both ways.
|
||||
|
||||
7c. Large/whole-body rewrite. Pick a method of 50+ lines and rewrite the entire
|
||||
body. Do it both ways.
|
||||
|
||||
8. Insert a new function/method at a specific structural location (for example,
|
||||
right after an existing method). Try both the symbolic-insert path and the
|
||||
manual Edit path.
|
||||
9. Rename a private helper used only within one file. Compare doing it by hand
|
||||
vs. using a semantic rename.
|
||||
|
||||
### Multi-file changes
|
||||
|
||||
10. Rename a symbol (function, class, or method) used across several files
|
||||
including imports. Compare the semantic path against the built-in equivalent
|
||||
chain.
|
||||
11. Move a symbol from one module to another, updating imports at all call
|
||||
sites. Use the semantic move tool if available; plan the built-in equivalent
|
||||
honestly.
|
||||
12. Move a file or package to a different location, updating imports at all call
|
||||
sites. Use the semantic move tool if available; plan the built-in equivalent
|
||||
honestly.
|
||||
12. Delete a symbol safely, checking it has no remaining usages. Compare
|
||||
search-then-delete with a safe-delete tool.
|
||||
13. Delete a symbol and propagate the deletion to all call sites. Compare to how
|
||||
the built-in equivalent would work.
|
||||
13. Inline a small helper into its call sites — only if the codebase contains a
|
||||
function that is legally inlinable. If no such candidate exists, report "no
|
||||
suitable candidate" and skip it.
|
||||
|
||||
### Reliability & correctness under correct use
|
||||
|
||||
14. Scope precision. Demonstrate that semantic tools address symbols by name
|
||||
path and can target a specific class method, override, or overload that text
|
||||
search would over-match.
|
||||
15. Atomicity. A semantic cross-file refactoring is atomic: either all sites are
|
||||
updated or none. A chain of `Edit` calls is not.
|
||||
16. Success signals. For each completed refactor, note what each tool returns on
|
||||
success.
|
||||
|
||||
### Workflow effects across multiple edits
|
||||
|
||||
17. Chain at least three edits in one file. Report what each toolset requires
|
||||
between edits.
|
||||
18. Multi-step exploration across the repo. Note whether intermediate results
|
||||
remain useful across later edits or have to be refreshed.
|
||||
|
||||
### Things where the comparison shouldn't be interesting
|
||||
|
||||
19. Read and understand a non-code file (config, changelog, docs, notebook).
|
||||
Semantic-code tools don't apply — use `Read`.
|
||||
20. Search for a free-text pattern across the repo (log string, magic constant,
|
||||
URL). Use `Grep`.
|
||||
|
||||
|
||||
|
||||
## Evaluation phase
|
||||
|
||||
Write a report structured for progressive disclosure.
|
||||
|
||||
**Value-weighting is required.** For every contribution or difference you
|
||||
identify — positive, neutral, or negative — estimate:
|
||||
|
||||
- **Frequency:** how often this arises in typical coding work
|
||||
- **Value per hit:** calls saved, tokens saved, or correctness impact
|
||||
|
||||
Order findings by **frequency × value-per-hit**, not novelty.
|
||||
|
||||
**Every section must end with a one-sentence verdict** summarizing the practical
|
||||
takeaway.
|
||||
|
||||
|
||||
|
||||
### 1. Headline: what Serena changes
|
||||
|
||||
Open with a precise description of the delta Serena provides. Distinguish
|
||||
between three categories: (a) tasks where Serena adds capability, (b) tasks
|
||||
where Serena applies but offers no improvement, and (c) tasks outside Serena's
|
||||
scope. Only category (b) constitutes a neutral or negative finding. Category (c)
|
||||
is context, not a finding.
|
||||
|
||||
A reader stopping here should understand both what is gained and what is not.
|
||||
|
||||
**Verdict:** (one sentence)
|
||||
|
||||
|
||||
|
||||
### 2. Added value and differences by area (3–6 bullets)
|
||||
|
||||
Each bullet must describe:
|
||||
|
||||
- What Serena changes relative to built-ins (positive, neutral, or negative)
|
||||
- Frequency
|
||||
- Value per hit
|
||||
|
||||
Avoid framing in terms of “wins”; describe concrete differences.
|
||||
|
||||
**Verdict:** (one sentence)
|
||||
|
||||
|
||||
|
||||
### 3. Detailed evidence, grouped by capability
|
||||
|
||||
Per task:
|
||||
|
||||
- What you attempted
|
||||
- Full call chain on both sides
|
||||
- Payloads sent and received
|
||||
|
||||
Include cases where:
|
||||
|
||||
- Serena is better
|
||||
- Built-ins are better
|
||||
- No meaningful difference exists
|
||||
|
||||
End each subsection with a verdict.
|
||||
|
||||
|
||||
|
||||
### 4. Token-efficiency analysis
|
||||
|
||||
Address:
|
||||
|
||||
- Payload differences across edit sizes
|
||||
- Forced reads
|
||||
- Stable vs ephemeral addressing
|
||||
|
||||
Include cases where each toolset is more efficient.
|
||||
|
||||
**Verdict:** (one sentence)
|
||||
|
||||
|
||||
|
||||
### 5. Reliability & correctness (under correct use)
|
||||
|
||||
Address:
|
||||
|
||||
- Precision of matching
|
||||
- Scope disambiguation
|
||||
- Atomicity
|
||||
- Semantic queries vs text search
|
||||
- External dependency symbol lookup and what setup it depends on
|
||||
|
||||
Include both strengths and limitations of each toolset.
|
||||
|
||||
**Verdict:** (one sentence)
|
||||
|
||||
|
||||
|
||||
### 6. Workflow effects across a session
|
||||
|
||||
Evaluate multi-step workflows and whether advantages compound or diminish.
|
||||
Include neutral or negative findings where applicable.
|
||||
|
||||
**Verdict:** (one sentence)
|
||||
|
||||
|
||||
|
||||
### 7. Unique capabilities (if any)
|
||||
|
||||
List capabilities that have no practical built-in equivalent. If none exist,
|
||||
explicitly state that. Annotate each with frequency and impact.
|
||||
|
||||
**Verdict:** (one sentence)
|
||||
|
||||
|
||||
|
||||
### 8. Tasks outside Serena's scope (built-in only)
|
||||
|
||||
Identify tasks where built-ins are the natural choice because Serena's tools
|
||||
don't target them. List these briefly for completeness but do not frame them as
|
||||
Serena shortcomings — they are outside its scope. Estimate their share of daily
|
||||
work to contextualize how much of a session Serena's augmentation covers.
|
||||
|
||||
**Verdict:** (one sentence)
|
||||
|
||||
|
||||
|
||||
### 9. Practical usage rule
|
||||
|
||||
Provide a decision rule for choosing between toolsets based on task type.
|
||||
|
||||
**Verdict:** (one sentence)
|
||||
|
||||
|
||||
|
||||
## What I'm looking for
|
||||
|
||||
- Claims grounded in observed evidence
|
||||
- Explicit reporting of **positive, neutral, and negative deltas**
|
||||
- Clear quantification of impact
|
||||
- Honest workflow-level comparisons
|
||||
|
||||
|
||||
|
||||
## What I am not looking for
|
||||
|
||||
- Misuse-based failure analysis
|
||||
- Gotcha comparisons
|
||||
- Neutral statements without evidence
|
||||
- Binary recommendations
|
||||
- Novelty-driven ordering
|
||||
- Unquantified claims
|
||||
```
|
||||
@@ -0,0 +1,12 @@
|
||||
# Summary Prompt
|
||||
|
||||
We used the prompt below to summarize the evaluation of Serena.
|
||||
|
||||
```
|
||||
Write a one-sentence user-facing summary about the value that Serena's tools
|
||||
provide for coding. With a bit of emotion but grounded in the evaluation that
|
||||
you have done above. It should be written from the perspective of a coding AI
|
||||
agent and make it clear whether an AI agent would ask its owner to add Serena's
|
||||
tools or not. It will be the first sentence that a potential new user will see
|
||||
about how useful Serena is for an AI agent.
|
||||
```
|
||||
@@ -0,0 +1,18 @@
|
||||
# Results
|
||||
|
||||
This section presents the results of the evaluation.
|
||||
|
||||
We performed evaluations using popular AI coding agents in representative scenarios — different
|
||||
agents, different programming languages, and different codebases — to show that the results are
|
||||
not specific to a single setup.
|
||||
|
||||
All evaluations were conducted using the **JetBrains-powered version** of Serena, as it is the
|
||||
more powerful backend with a broader set of refactoring and navigation capabilities. The
|
||||
evaluation can easily be repeated with the LSP-based backend to assess its subset of capabilities.
|
||||
|
||||
- [Claude Code (Opus 4.6) on a large Python codebase](010_cc_on_tianshou)
|
||||
- [Codex (GPT 5.4) on a Java codebase](020_codex_on_jbplugin)
|
||||
- [Copilot CLI (GPT 5.4) on a large, multi-language monorepo](030_copilot_cli_on_ente.md)
|
||||
|
||||
You can run your own evaluation on a project of your choice by reusing our
|
||||
[evaluation prompt](../020_prompts/010_evaluation-prompt.md).
|
||||
@@ -0,0 +1,358 @@
|
||||
:::{admonition} Evaluation Result
|
||||
:class: note
|
||||
**Generated by**: Claude Opus 4.6 (coding AI agent in Claude Code CLI)
|
||||
**Codebase:** [Tianshou](https://github.com/thu-ml/tianshou) — a Python reinforcement learning library (~26K lines, 43 source files)
|
||||
:::
|
||||
|
||||
# Claude Code (Opus 4.6, medium)
|
||||
|
||||
> **One-line summary:** Serena's IDE-backed semantic tools are the single most impactful addition to my toolkit — cross-file renames, moves, and reference lookups that would cost me 8–12 careful, error-prone steps collapse into one atomic call, and I would absolutely ask any developer I work with to set them up.
|
||||
|
||||
**Method:** Hands-on, side-by-side execution of 20 tasks using both toolsets. All edits were applied to real files and verified via `git diff`, then reverted.
|
||||
|
||||
---
|
||||
|
||||
## 1. Headline: What Serena Changes
|
||||
|
||||
Serena adds an IDE-backed semantic layer (powered by JetBrains) on top of the built-in text-level tools. The practical delta breaks down into three categories:
|
||||
|
||||
**(a) Tasks where Serena adds capability:**
|
||||
- **Cross-file refactoring** (rename, move symbol, move file): 1 atomic call vs. N×(Grep+Read+Edit) chains. This is Serena's strongest contribution — it collapses multi-file, import-aware operations into single calls that are atomic and semantically correct.
|
||||
- **Structural code navigation**: Symbol overviews, type hierarchies, and reference finding return structured, scope-disambiguated results that text search cannot produce without manual interpretation.
|
||||
- **External dependency introspection**: Serena can look up symbols in installed packages via the IDE index without requiring manual environment discovery.
|
||||
|
||||
**(b) Tasks where Serena applies but offers no improvement:**
|
||||
- **Single-file rename of a unique string**: `Edit` with `replace_all=true` achieves the same result in 1 call (after a Read), with comparable effort.
|
||||
- **Small edits (1–3 lines inside a method)**: Edit's substring matching is more efficient — less payload sent, no requirement to supply the full symbol body.
|
||||
- **Inserting new code at a known location**: Both approaches require ~1–2 calls. Edit can target by surrounding text; Serena targets by name path. The effort is comparable.
|
||||
|
||||
**(c) Tasks outside Serena's scope (built-in only):**
|
||||
- Non-code files (config, docs, notebooks, changelogs)
|
||||
- Free-text search across the repo
|
||||
- Shell commands, git operations, test execution
|
||||
- File creation from scratch
|
||||
|
||||
**Verdict:** Serena's primary contribution is collapsing multi-file, semantically-aware operations (rename, move, reference-finding, type hierarchy) from multi-step manual processes into single atomic calls; for single-file text edits, it is comparable or slightly less efficient.
|
||||
|
||||
---
|
||||
|
||||
## 2. Added Value and Differences by Area
|
||||
|
||||
**1. Cross-file rename/move (Serena: strong positive)**
|
||||
- Serena: 1 call updates definition + all imports + all usages across N files atomically.
|
||||
- Built-in: 1 Grep + N×(Read+Edit) = 2N+1 calls, non-atomic.
|
||||
- Frequency: Moderate (a few times per working session during refactoring work). Value per hit: High — saves 6–10+ calls and eliminates the risk of partial updates.
|
||||
|
||||
**2. Structural overview / targeted symbol reading (Serena: moderate positive)**
|
||||
- `get_symbols_overview(depth=1)`: 1 call returns all classes, methods, and attributes in structured JSON, via the IDE's parser — language-agnostic and always correct. Built-in equivalent: `Grep` for a language-specific heuristic pattern like `^(class |def )`. This works for simple Python files but is inherently fragile: the pattern must be hand-tuned per language, misses decorated or multi-line declarations, and can match inside strings or comments. For non-Python languages, an entirely different regex would be needed.
|
||||
- `find_symbol(include_body=True)`: Retrieves a specific method body by name path without reading surrounding code. Built-in: requires knowing the line number (via Grep) then Read with offset — 2 calls.
|
||||
- Frequency: High (many times per session). Value per hit: Low-to-moderate — saves ~1 call and some context window tokens per navigation. The reliability advantage over heuristic Grep patterns is consistent across all languages.
|
||||
|
||||
**3. Reference finding with structural context (Serena: moderate positive)**
|
||||
- `find_referencing_symbols`: Returns which *symbols* reference a target, with file grouping and usage type (import, call, type annotation). Built-in `Grep` returns all text matches including docs, comments, and string literals — same recall but lower precision.
|
||||
- Frequency: Moderate. Value per hit: Moderate — precision matters when planning refactors of widely-used symbols.
|
||||
|
||||
**4. Type hierarchy (Serena: positive, niche)**
|
||||
- 1 call returns full super/sub-type chains transitively. Built-in: requires iterative Grep for `class X(Y)` patterns, manual transitive closure.
|
||||
- Frequency: Low (occasional during architecture exploration). Value per hit: High per occurrence — several calls saved.
|
||||
|
||||
**5. Stable addressing across edits (Serena: moderate positive)**
|
||||
- Serena addresses symbols by name path (`CollectStats/refresh_return_stats`), which is invariant under edits elsewhere in the file. The built-in workflow is fundamentally line-number-mediated: Grep returns line numbers, Read takes line offsets — and both go stale after any edit to the file. Edit's text matching (`old_string`) is more resilient than line numbers, but it sits at the end of a chain that starts with position-based lookups. After an edit shifts lines, previously noted Grep results and Read offsets are invalid and must be re-acquired.
|
||||
- This means a multi-edit session with built-ins requires re-Grep or re-Read between edits to re-establish positions, while Serena's name paths remain valid throughout. In Task 17 (three chained edits), Serena needed 3 calls total; the built-in path needed 4 (1 Read + 3 Edits) only because the text anchors happened to be unique — but had any Edit failed uniqueness, a re-Read would have been required, pushing the count to 5–7.
|
||||
- Frequency: High (any session with multiple edits to the same file). Value per hit: Moderate — saves 1–2 re-read calls per edit chain, and eliminates the class of errors where stale line numbers cause edits to land in the wrong place.
|
||||
|
||||
**6. Single-file small/medium edits (Serena: neutral to slight negative)**
|
||||
- For a 1-line change inside a 13-line method, Serena's `replace_symbol_body` requires sending the entire 13-line body. Edit sends just the changed line. The prerequisite cost differs: Serena needs `find_symbol(include_body=True)` to get the body; Edit needs `Read` of the relevant lines. Both are 1 prerequisite call + 1 edit call.
|
||||
- Frequency: Very high. Value per hit: Slightly negative for Serena — more payload for small changes.
|
||||
|
||||
**Verdict:** Serena's strongest contributions are in cross-file refactoring (high value, moderate frequency) and structured navigation (moderate value, high frequency); for small text edits, built-ins are slightly more efficient.
|
||||
|
||||
---
|
||||
|
||||
## 3. Detailed Evidence, Grouped by Capability
|
||||
|
||||
### 3.1 Structural Overview (Task 2)
|
||||
|
||||
| Metric | Serena `get_symbols_overview(depth=1)` | Built-in `Grep` for class/def |
|
||||
|---|---|---|
|
||||
| Calls | 1 | 1 |
|
||||
| Output structure | JSON tree: 14 classes with nested methods + attributes | Flat list: 54 `class`/`def` lines with line numbers |
|
||||
| Nesting visible? | Yes — methods grouped under classes | No — indentation implies nesting but no grouping |
|
||||
| Attributes visible? | Yes | No |
|
||||
| Output size | ~2.5KB structured JSON | ~3KB flat text |
|
||||
| Correctness | Always correct — uses IDE parser, language-agnostic | Heuristic — the regex `^(class \|def \| def )` is Python-specific and can miss decorated methods, multi-line signatures, or match inside strings/comments |
|
||||
| Language portability | Works unchanged for any language the IDE supports | Requires a new hand-tuned regex per language |
|
||||
| Next step | `find_symbol("ClassName/method", include_body=True)` — 1 call | `Read(file, offset=line, limit=N)` — 1 call |
|
||||
|
||||
Both paths need 1 call for overview + 1 call for drill-down. Serena's output is more structured and reliably correct; Grep's is simpler but fragile. The Grep pattern used here happened to work well for this Python codebase, but would need to be rewritten for Java (`class|interface|enum`, brace-delimited), Rust (`fn|struct|impl|trait`), TypeScript (`class|function|interface`), etc. — and even within Python, it misses cases like `@overload`-decorated methods or methods whose `def` line is preceded by a long decorator stack that pushes `def` to a non-standard indentation.
|
||||
|
||||
**Verdict:** Serena provides a reliably correct structural overview across languages; Grep-based overviews are heuristic approximations that work in simple cases but degrade for complex declarations or non-Python codebases.
|
||||
|
||||
### 3.2 Targeted Method Retrieval (Task 3)
|
||||
|
||||
| Metric | Serena | Built-in |
|
||||
|---|---|---|
|
||||
| Calls | 1 (`find_symbol` with `include_body=True`) | 2 (Grep to find line + Read with offset) |
|
||||
| Prerequisite | Know the name path | Know the method name |
|
||||
| Output | Method body only (no surrounding code) | Surrounding code included (must estimate limit) |
|
||||
|
||||
For `Collector/_collect` (330 lines): Serena returned exactly 330 lines. Built-in Read returned 332 lines (including the next method's signature).
|
||||
|
||||
**Verdict:** Serena saves 1 call and returns precisely the requested body; built-in is adequate but requires line-number discovery.
|
||||
|
||||
### 3.3 Cross-File References (Task 4)
|
||||
|
||||
| Metric | Serena `find_referencing_symbols` | Built-in `Grep` |
|
||||
|---|---|---|
|
||||
| Calls | 1 | 1 |
|
||||
| Files found | 63 (code files only, structured by symbol type) | 83 (includes docs, notebooks, changelogs, README) |
|
||||
| Output structure | Grouped by file, categorized (import, call, type annotation) | Flat file list |
|
||||
| Precision | Code-only references | All textual mentions |
|
||||
|
||||
Grep finds 83 files including `README.md`, `CHANGELOG.md`, and `.ipynb` notebooks. Serena finds 63 code files. For the question "who uses this in code?", Serena's result is directly usable. For "where is this mentioned anywhere?", Grep is the right tool.
|
||||
|
||||
**Verdict:** Serena provides higher-precision code-usage results; Grep provides broader text-level coverage. Different tools for different questions.
|
||||
|
||||
### 3.4 Type Hierarchy (Task 5)
|
||||
|
||||
| Metric | Serena `type_hierarchy` | Built-in |
|
||||
|---|---|---|
|
||||
| Calls | 1 | 2+ (Grep for `class X(BaseCollector` + Grep for `class Collector(` + potential transitive search) |
|
||||
| Result | `BaseCollector → ABC → object` (supers), `BaseCollector → Collector → AsyncCollector` (subs), with file locations | `class Collector(BaseCollector[TCollectStats], ...)` at line 551 — requires further reads for AsyncCollector's superclass |
|
||||
|
||||
**Verdict:** Serena produces the full transitive hierarchy in 1 call; built-in requires iterative searches.
|
||||
|
||||
### 3.5 External Dependency Lookup (Task 6)
|
||||
|
||||
| Metric | Serena | Built-in |
|
||||
|---|---|---|
|
||||
| Can retrieve? | Yes — `find_declaration` + `search_deps=True` | Requires environment discovery: `python -c "import X; print(inspect.getfile(X))"` then `Read` |
|
||||
| Infrastructure | IDE index (pre-built) | Working Python environment, correct venv activated |
|
||||
| Result | Symbol location + documentation | Full source file (if env is set up) |
|
||||
|
||||
In this session, the Python environment wasn't directly accessible from bash, so built-in lookup would have required additional setup. Serena retrieved `torch.distributions.Distribution`'s location and docstring via the IDE index.
|
||||
|
||||
**Verdict:** Serena provides dependency introspection without environment setup; built-in requires a working interpreter.
|
||||
|
||||
### 3.6 Small Edit — 1 Line Change (Task 7a)
|
||||
|
||||
| Metric | Serena `replace_symbol_body` | Built-in `Edit` |
|
||||
|---|---|---|
|
||||
| Prerequisite calls | 1 (`find_symbol` with `include_body=True`) — already done in Task 3 | 1 (`Read` of ~15 lines) |
|
||||
| Edit call | 1 (send full 13-line body) | 1 (send 1-line old + 1-line new) |
|
||||
| Payload sent (edit call) | ~550 chars (full body) | ~120 chars (changed line only) |
|
||||
| Total calls | 1–2 | 2 |
|
||||
|
||||
**Verdict:** For small changes, Edit sends ~4.5× less payload in the edit call. Total call count is the same.
|
||||
|
||||
### 3.7 Medium Rewrite — ~19 Lines (Task 7b)
|
||||
|
||||
| Metric | Serena | Built-in |
|
||||
|---|---|---|
|
||||
| Prerequisite | 1 `find_symbol` (already done) | 1 `Read` (~22 lines) |
|
||||
| Edit payload | ~550 chars (full body) | ~1000 chars (old 19 lines + new 20 lines) |
|
||||
| Total calls | 1–2 | 2 |
|
||||
|
||||
At this scale, Serena's payload is actually *smaller* because Edit must send both old and new text, while Serena sends only the new body. The crossover point is approximately where the changed region exceeds half the method body.
|
||||
|
||||
**Verdict:** At medium scale, payloads converge; Serena's is slightly smaller due to only sending the replacement.
|
||||
|
||||
### 3.8 Large Rewrite — 55 Lines (Task 7c)
|
||||
|
||||
| Metric | Serena | Built-in |
|
||||
|---|---|---|
|
||||
| Prerequisite | 1 `find_symbol` (already done) | 1 `Read` (~67 lines) |
|
||||
| Edit payload | ~2200 chars (new body) | ~4400 chars (old 63 lines + new 62 lines) |
|
||||
| Total calls | 1–2 | 2 |
|
||||
|
||||
**Verdict:** For full-method rewrites, Serena sends ~50% less payload because it only sends the replacement, not the original.
|
||||
|
||||
### 3.9 Cross-File Rename (Task 10)
|
||||
|
||||
| Metric | Serena `rename` | Built-in chain |
|
||||
|---|---|---|
|
||||
| Calls | 1 | 1 Grep + 4 Read + 4 Edit = 9 |
|
||||
| Files affected | 4 (automatically discovered) | 4 (manually discovered via Grep) |
|
||||
| Import handling | Automatic | Manual — must identify and rewrite each import statement |
|
||||
| Atomicity | Atomic — all-or-nothing | Sequential — intermediate states are inconsistent |
|
||||
|
||||
**Verdict:** Serena reduces a 9-call chain to 1 call for cross-file rename, with atomicity.
|
||||
|
||||
### 3.10 Move Symbol Across Modules (Task 11)
|
||||
|
||||
| Metric | Serena `move` | Built-in equivalent |
|
||||
|---|---|---|
|
||||
| Calls | 1 | ~8–12 (Read source, Edit source to remove, Edit target to add, Grep for imports, Read+Edit each import site) |
|
||||
| Import updates | Automatic | Manual — must rewrite `from X import Y` → `from Z import Y` in each file |
|
||||
| Dependency imports | Automatically adds needed imports to target file | Must manually inspect what the moved function imports and replicate |
|
||||
|
||||
Moving `get_stddev_from_dist` from `collector.py` to `stats.py`: Serena updated 3 files (source, target, test) in 1 call, including adding necessary imports to the target module.
|
||||
|
||||
**Verdict:** Move is Serena's highest-value single operation — it handles import graph updates that would be error-prone manually.
|
||||
|
||||
### 3.11 Move File (Task 12a)
|
||||
|
||||
| Metric | Serena `move` | Built-in equivalent |
|
||||
|---|---|---|
|
||||
| Calls | 1 | 1 `git mv` + 1 Grep + N×(Read+Edit) for import updates = ~12 |
|
||||
| Files updated | 5 import updates automatic | Must manually discover and rewrite |
|
||||
|
||||
**Verdict:** Same pattern as symbol move — Serena collapses an N-step process.
|
||||
|
||||
### 3.12 Safe Delete (Task 12b)
|
||||
|
||||
Serena's `safe_delete` in safe mode (default):
|
||||
- Reports all usages before deleting — acts as a guard.
|
||||
- For unused symbols, deletes cleanly in 1 call.
|
||||
- For used symbols, refuses and lists usages.
|
||||
|
||||
Built-in equivalent: Grep to check usages → if none found, Read + Edit to delete. 2–3 calls.
|
||||
|
||||
**Verdict:** Safe delete adds a safety check that the built-in path must implement manually.
|
||||
|
||||
### 3.13 Inline (Task 13)
|
||||
|
||||
Serena's inline tool did not work for any Python function tested. The JetBrains backend does not support Python function inlining (this is a language-level limitation of the IDE's refactoring engine, not a Serena bug).
|
||||
|
||||
**Verdict:** No candidate inlinable in this codebase; inline capability appears unavailable for Python.
|
||||
|
||||
### 3.14 Scope Precision (Task 14)
|
||||
|
||||
Three methods named `reset_env` exist in `collector.py` (in `BaseCollector`, `Collector`, `AsyncCollector`).
|
||||
|
||||
- Serena: `find_symbol("Collector/reset_env")` returns exactly that override's body.
|
||||
- Grep: Returns 3 line numbers. Determining which belongs to which class requires reading surrounding context.
|
||||
|
||||
**Verdict:** Serena's name-path addressing eliminates class-level disambiguation that Grep requires.
|
||||
|
||||
### 3.15 Chained Edits (Task 17)
|
||||
|
||||
Three consecutive edits to methods in `CollectStats`:
|
||||
- Serena: 3 `replace_symbol_body` calls, 0 intermediate reads. Name paths (`CollectStats/refresh_return_stats`, `CollectStats/refresh_len_stats`, `CollectStats/refresh_std_array_stats`) remained valid across all edits because they are structural identifiers, not positions.
|
||||
- Built-in: 1 Read + 3 Edit calls = 4 calls in the best case. The Read was required upfront (Edit enforces "must read before editing"). The 3 Edits succeeded without re-reads only because the `old_string` anchors happened to remain unique after each edit. But this is fragile: Edit also enforces a "file has been modified since read" check when external tools modify the file, forcing a re-Read. More fundamentally, if I had needed to *find* these methods first (the typical case when you don't already know the line numbers), each Grep result from before the first edit would have been stale after it — line 232 is no longer line 232 after inserting 2 lines above it.
|
||||
|
||||
The core asymmetry: Serena's addressing is *structural* (survives edits by definition), while built-in addressing is *positional* (line numbers from Grep/Read go stale after any insertion or deletion). Edit's text matching partially mitigates this, but only for the final step — the discovery steps (Grep, Read with offset) remain position-dependent.
|
||||
|
||||
**Verdict:** Serena's name-path stability eliminates the re-read/re-grep cycle between edits; built-in tools require re-acquiring positions after each edit that shifts line numbers.
|
||||
|
||||
---
|
||||
|
||||
## 4. Token-Efficiency Analysis
|
||||
|
||||
### Payload by edit size
|
||||
|
||||
| Edit scale | Serena payload (edit call) | Edit payload (edit call) | Winner |
|
||||
|---|---|---|---|
|
||||
| 1-line change in 13-line method | ~550 chars (full body) | ~120 chars | Edit (~4.5×) |
|
||||
| 19-line medium rewrite | ~550 chars | ~1000 chars | Serena (~1.8×) |
|
||||
| 55-line full rewrite | ~2200 chars | ~4400 chars | Serena (~2×) |
|
||||
| Cross-file rename (4 files) | ~100 chars | ~800 chars (4 Edit calls) | Serena (~8×) |
|
||||
|
||||
### Prerequisite reads
|
||||
|
||||
- Serena: `find_symbol(include_body=True)` returns the symbol body. If you've already navigated to it during exploration, no additional call needed.
|
||||
- Built-in: `Read` with offset/limit. Always required before Edit (enforced by the tool).
|
||||
|
||||
### Stable vs ephemeral addressing
|
||||
|
||||
- Serena's name paths (`CollectStats/refresh_return_stats`) are stable identifiers — they survive any edit to surrounding code. No re-read or re-discovery needed between edits.
|
||||
- The built-in workflow uses ephemeral, position-based addresses at every stage: Grep returns line numbers, Read takes line offsets, and both are invalidated by any insertion or deletion above the target. Edit's `old_string` matching is content-based and more resilient, but it depends on the upstream position-based steps to know *what* to match. After an edit shifts line numbers, the entire Grep→Read→Edit chain must be re-executed from the top.
|
||||
- In a session with N edits to the same file, this costs up to N-1 additional Grep/Read round-trips with built-ins. With Serena, the cost is zero — the same name path works on the first and tenth edit.
|
||||
|
||||
**Verdict:** Serena is more token-efficient for medium-to-large edits and cross-file operations; Edit is more efficient for small, localized changes. Across multi-edit sessions, Serena's stable addressing avoids the re-read tax that compounds with each successive built-in edit.
|
||||
|
||||
---
|
||||
|
||||
## 5. Reliability & Correctness (Under Correct Use)
|
||||
|
||||
### Precision of matching
|
||||
- Serena: Exact symbol resolution via name paths. `Collector/reset_env` unambiguously selects one method.
|
||||
- Edit: Text matching. Unique strings match correctly; non-unique strings fail (and Edit reports the error).
|
||||
|
||||
### Scope disambiguation
|
||||
- Serena distinguishes overrides, overloads (via indices), and nested classes by name path.
|
||||
- Grep/Edit cannot distinguish methods with the same name in different classes without reading surrounding context.
|
||||
|
||||
### Atomicity
|
||||
- Serena's cross-file operations (rename, move) are atomic — all files updated or none.
|
||||
- Built-in multi-file edits are sequential — a failure mid-chain leaves an inconsistent state (recoverable via `git checkout`).
|
||||
|
||||
### Semantic queries vs text search
|
||||
- `find_referencing_symbols` returns code-level references categorized by type. Grep returns all textual mentions.
|
||||
- `type_hierarchy` returns transitive sub/supertypes. No built-in equivalent without iterative search.
|
||||
|
||||
### External dependency lookup
|
||||
- Serena: Available via IDE index, no environment setup needed. Can retrieve symbol docs and location.
|
||||
- Built-in: Requires working Python environment, correct venv, and manual file discovery. More powerful when available (full source), but higher setup cost.
|
||||
|
||||
**Verdict:** Serena provides stronger correctness guarantees for symbol-level operations (scope, atomicity, semantic precision); built-in tools are reliable for text-level operations with the caveat that scope disambiguation requires manual effort.
|
||||
|
||||
---
|
||||
|
||||
## 6. Workflow Effects Across a Session
|
||||
|
||||
### Compound advantages
|
||||
- **Exploration → Edit without position re-acquisition**: Serena's overview tools produce name paths that are directly usable as edit targets. The built-in path produces line numbers (from Grep) that are consumed by Read — but after an edit, those line numbers are stale. In a typical explore-edit-explore-edit cycle, Serena's name paths remain valid throughout while built-in line numbers must be re-acquired after each edit. Over 3 explore+edit cycles, this saves ~3–6 intermediate Grep/Read calls.
|
||||
- **Chained edits without re-reads**: Name-path stability means no re-reads between edits to the same file. For N edits, this saves up to N-1 Read calls. Edit's text matching partially avoids this (if old_strings stay unique), but the upstream discovery steps (Grep line numbers, Read offsets) still go stale.
|
||||
- **Cross-file refactoring**: When a rename or move is part of a larger change, doing it atomically avoids the need to manually track "which files still need updating."
|
||||
|
||||
### Diminishing returns
|
||||
- For purely exploratory sessions (reading code, no edits), Serena's advantage is modest — `Grep` and `Read` are nearly as fast for navigation, and Serena's structured output doesn't save many calls.
|
||||
- For sessions dominated by small text edits (config changes, log message tweaks), Serena adds no value.
|
||||
|
||||
### Neutral findings
|
||||
- Both toolsets require similar total calls for single-file work. The difference is ~1 call per operation.
|
||||
- Output quality for code review (reading diffs, understanding changes) is identical — both require `git diff`.
|
||||
|
||||
**Verdict:** Serena's advantages compound across multi-step refactoring sessions where each operation feeds into the next; for read-heavy or small-edit sessions, the advantage is marginal.
|
||||
|
||||
---
|
||||
|
||||
## 7. Unique Capabilities (No Practical Built-In Equivalent)
|
||||
|
||||
1. **Atomic cross-file rename** — 1 call, all imports and usages updated, all-or-nothing. No built-in equivalent without scripting a multi-step chain. Frequency: moderate (refactoring sessions). Impact: high (saves 5–10 calls, eliminates partial-update risk).
|
||||
|
||||
2. **Atomic cross-file move** (symbol or file) with import rewriting — includes adding necessary imports to the target module. Frequency: low-to-moderate. Impact: very high per occurrence (saves 8–12 calls, handles import dependency graph).
|
||||
|
||||
3. **Type hierarchy traversal** — transitive supertypes and subtypes in 1 call. Built-in requires iterative Grep with manual transitive closure. Frequency: low. Impact: moderate (saves 3–5 calls).
|
||||
|
||||
4. **Safe delete with usage check** — reports all usages before deleting, with option to propagate. Built-in requires Grep + manual verification. Frequency: low. Impact: moderate (safety check is the value).
|
||||
|
||||
5. **External dependency symbol lookup** via IDE index — no Python environment needed. Frequency: moderate. Impact: moderate (avoids environment setup friction).
|
||||
|
||||
**Verdict:** Serena provides 3–5 capabilities with no practical built-in equivalent, concentrated in cross-file refactoring and semantic navigation.
|
||||
|
||||
---
|
||||
|
||||
## 8. Tasks Outside Serena's Scope (Built-In Only)
|
||||
|
||||
- **Non-code file operations**: Reading/editing config files, docs, changelogs, notebooks → `Read`/`Edit`/`Write`
|
||||
- **Free-text search**: Finding log strings, URLs, magic constants → `Grep`
|
||||
- **Shell operations**: Running tests, builds, git commands, package management → `Bash`
|
||||
- **File creation**: New files from scratch → `Write`
|
||||
- **Glob-based file discovery**: Finding files by pattern → `Glob`
|
||||
- **Broad codebase search**: When you don't know what you're looking for → `Grep` with regex
|
||||
|
||||
Estimated share of daily work: These tasks constitute roughly 40–60% of a typical coding session (reading docs, running tests, searching for patterns, editing config). Serena's augmentation covers the remaining 40–60% where code-level semantic operations apply.
|
||||
|
||||
**Verdict:** Built-in tools handle roughly half of daily work that falls entirely outside Serena's scope; Serena augments the code-centric other half.
|
||||
|
||||
---
|
||||
|
||||
## 9. Practical Usage Rule
|
||||
|
||||
| Task type | Use |
|
||||
|---|---|
|
||||
| Cross-file rename, move, delete | Serena (unique capability) |
|
||||
| Understanding class hierarchy or symbol relationships | Serena (1 call vs. iterative search) |
|
||||
| Getting a structural overview of a large file | Serena (richer structure) or Grep (simpler, faster) |
|
||||
| Reading a specific method body by name | Serena (direct) or Grep+Read (2 calls) |
|
||||
| Small edit (1–5 lines inside a method) | Edit (less payload) |
|
||||
| Full method rewrite | Serena `replace_symbol_body` (less payload, no re-read) |
|
||||
| Single-file rename of a unique identifier | Edit with `replace_all` (equivalent) |
|
||||
| Non-code files, config, docs | Built-in `Read`/`Edit` |
|
||||
| Text search, pattern matching | Built-in `Grep` |
|
||||
| Anything involving shell, git, tests | Built-in `Bash` |
|
||||
| External dependency inspection | Serena (no env setup needed) |
|
||||
|
||||
**Verdict:** Use Serena for cross-file refactoring and semantic navigation; use built-ins for text-level edits, non-code files, and shell operations; for single-file code edits, choose based on edit size (Edit for small, Serena for full-body rewrites).
|
||||
@@ -0,0 +1,665 @@
|
||||
:::{admonition} Evaluation Result
|
||||
:class: note
|
||||
**Generated by:** GPT-5.4 (high) in Codex
|
||||
**Codebase:** Serena JetBrains Plugin (Java)
|
||||
:::
|
||||
|
||||
# Codex (GPT-5.4, high)
|
||||
|
||||
|
||||
> **One-line summary:** As a coding agent, I would ask my owner to add Serena because it turns fragile text-and-line-number work into precise symbol-aware navigation and
|
||||
refactoring, which makes real code changes feel faster, safer, and far less blind.
|
||||
|
||||
This report compares Serena's JetBrains-backed semantic coding tools with built-in file, shell, search, and patch tools in this repository. The comparison assumes competent use of both toolsets: built-ins are used for text, file, shell, config, and small patch work; Serena is used where symbol identity, language semantics, or IDE refactoring semantics apply.
|
||||
|
||||
Method: I explored code first, avoided repo documentation and prior notes, ran real edits/refactors, and reverted after each experiment. After each edit/refactor experiment I checked `git status --short` and returned the tree to clean before moving on. Measurements are approximate, but call counts, diff sizes, and result shapes are from observed runs.
|
||||
|
||||
## 1. Headline: what Serena changes
|
||||
|
||||
Serena adds a semantic layer over the codebase. Its concrete delta is the ability to address and transform code by symbols: name paths, overload indexes, reference graphs, type hierarchies, external declarations, and JetBrains refactoring operations.
|
||||
|
||||
Tasks where Serena adds capability:
|
||||
|
||||
- Code-reference search: `find_referencing_symbols` for `Symbol/findReferences` returned 6 precise code usages grouped by containing symbols. `rg findReferences` also returned the declaration, the `/findReferences` route string, and comments.
|
||||
- Type hierarchy: one `type_hierarchy` call for `PostRequestHandler` returned 18 direct endpoint subclasses, a transitive `TypeHierarchyHandler -> GetSupertypesHandler/GetSubtypesHandler` branch, and the external `Object` supertype.
|
||||
- External dependency lookup: `find_declaration` resolved `ReferencesSearch.search(anchorElement)` to `<ext:ReferencesSearch.class|466808a0>`, and `find_symbol` returned the selected overload body. Built-in repo/cache grep found the import and call but not the dependency definition.
|
||||
- Cross-file refactors: semantic rename, symbol move, file move, safe delete, and inline executed real IDE refactors and updated files/usages/imports.
|
||||
- Stable addressing: a chained edit used `SymbolFinder/findFilesByName`, `SymbolFinder/getProject`, and `SymbolFinder/qualNameMatchesName` after earlier edits shifted line numbers.
|
||||
|
||||
Tasks where Serena applies but offered little or no improvement:
|
||||
|
||||
- Tiny intra-method edits: a 1-line error-message change was smaller with a built-in patch; Serena body replacement required sending the whole method.
|
||||
- Simple one-file private rename: manual patch and semantic rename produced the same 2-line diff for `qualNameMatchesName -> qualifiedNameMatchesName`.
|
||||
- Method insertion at a known spot: both workflows produced the same 4-line diff; Serena's benefit was target stability, not smaller payload.
|
||||
- Whole-method rewrite: Serena targets the method boundary cleanly, but the full replacement body still has to be sent.
|
||||
|
||||
Tasks outside Serena's scope:
|
||||
|
||||
- Reading config/non-code files such as `plugin.xml` and `build.gradle.kts`.
|
||||
- Free-text searches for URLs, endpoint strings, settings values, comments, and resources.
|
||||
- File inventory, line counts, shell commands, build/test execution, Git operations, and arbitrary text edits.
|
||||
|
||||
Value-weighted summary:
|
||||
|
||||
| Rank | Difference | Frequency | Value per hit | Delta |
|
||||
|---|---:|---:|---:|---|
|
||||
| 1 | Cross-file semantic refactors | Medium-high | High | Often avoids 5-20 manual edit/search/verify steps and reduces partial-update risk. |
|
||||
| 2 | Code usages vs text mentions | High | Medium-high | Removes noisy grep triage for "who uses this symbol?" |
|
||||
| 3 | Symbol overview/body retrieval | High | Medium | Avoids large reads and stale line targeting. |
|
||||
| 4 | Type hierarchy/implementations | Medium | High | Hard to reproduce correctly with text search, especially transitively. |
|
||||
| 5 | Stable addressing across edit chains | Medium | Medium | Reduces refresh work after line shifts. |
|
||||
| 6 | External dependency lookup | Low-medium | High | Needs IDE index; built-ins need source/decompiler infrastructure. |
|
||||
| 7 | Small local edits | High | Neutral/negative for Serena | Built-in patch/edit usually sends less. |
|
||||
|
||||
**Verdict:** Serena materially changes symbol-centric exploration and refactoring; it does not replace built-ins for ordinary text, file, shell, config, or tiny local edits.
|
||||
|
||||
## 2. Added value and differences by area
|
||||
|
||||
- Cross-file semantic refactoring changes both workflow and correctness. Frequency: medium-high. Value per hit: high. The observed class rename updated a file/class plus imports/usages; the nested-record move changed 4 existing files and created 1 new file; the file move updated package/imports across 3 paths. Built-ins can reproduce the result, but only through search, file moves, patches, import cleanup, and verification.
|
||||
|
||||
- Semantic search separates code usage from text mention. Frequency: high. Value per hit: medium-high. Serena returned only code references for `Symbol/findReferences`; `rg` returned code, route strings, comments, and the declaration. Built-ins remain better for "mentioned anywhere."
|
||||
|
||||
- Symbol overview/body retrieval cuts exploration payload. Frequency: high. Value per hit: medium. `get_symbols_overview` on `Symbol.java` returned fields, methods, nested classes, and overload indexes without reading the 1,000+ line file. Regex produced line-oriented candidates and still required manual scope interpretation.
|
||||
|
||||
- Type/dependency queries add capabilities not practically present in built-ins. Frequency: low-medium. Value per hit: high. Serena returned a transitive type graph and an external IntelliJ API method body; text tools needed iterative searches or extra source/decompiler setup.
|
||||
|
||||
- Small localized edits are not improved by symbol-body replacement. Frequency: high. Value per hit: neutral or negative for Serena. A one-line patch was smaller than replacing an 11-line method body.
|
||||
|
||||
- Some JetBrains refactors staged added/deleted files. Frequency: limited to class/file move/delete style operations. Value per hit: neutral operational tradeoff. Semantic value remains, but verification/revert must check the index as well as unstaged files.
|
||||
|
||||
**Verdict:** Serena's highest-value differences are symbol identity, reference graphs, and IDE refactor execution; built-ins remain superior for simple text-local work.
|
||||
|
||||
## 3. Detailed evidence, grouped by capability
|
||||
|
||||
### 3.1 Repository structure and entry points
|
||||
|
||||
Attempted: identify top-level layout, packages, and entry points.
|
||||
|
||||
Built-in call chain:
|
||||
|
||||
1. `git status --short` -> clean.
|
||||
2. `rg --files -g '!*.md' -g '!docs/**' -g '!CLAUDE.md'` -> code/config/resource inventory.
|
||||
3. `Get-ChildItem -Force` -> top-level directories.
|
||||
4. `Get-Content src/main/resources/META-INF/plugin.xml` -> plugin entry points.
|
||||
5. `Get-Content build.gradle.kts -TotalCount 80` -> Gradle/IntelliJ platform setup.
|
||||
|
||||
Serena call chain: not applicable for repository inventory and non-code config.
|
||||
|
||||
Observed structure:
|
||||
|
||||
- `service`: backend service and request handling.
|
||||
- `service/endpoint`: endpoint handlers for symbol search, references, formatting, rename, move, safe delete, inline, inspections, and completions.
|
||||
- `symbol`: symbol model, symbol lookup, hierarchy, path matching, and move processors.
|
||||
- `util`: IDE/project/editor/UI helpers.
|
||||
- `ui`: tool window content.
|
||||
- `plugin.xml`: registers `PluginStartupActivity`, settings service/configurable, and a dummy test action.
|
||||
|
||||
Payloads: built-ins used 5 small shell/read calls and returned several KB of code/config context; Serena had no relevant semantic operation.
|
||||
|
||||
**Verdict:** Repository layout and non-code entry-point discovery are built-in work; Serena starts adding value once the target is code symbols.
|
||||
|
||||
### 3.2 Large file structural overview
|
||||
|
||||
Attempted: compare structural overview on `src/main/java/de/oraios/serena/symbol/Symbol.java`.
|
||||
|
||||
Serena call chain:
|
||||
|
||||
1. `get_symbols_overview(relative_path=Symbol.java, depth=1)`
|
||||
2. Next step: `find_symbol(Symbol/findReferences, include_body=true)`
|
||||
|
||||
Serena output: one top-level `Symbol` class; fields; nested `ChildrenCollector` and `DocumentationResolver`; methods including overload-indexed names such as `getLocationString[0]`, `getLocationString[1]`, `move[0]`, `move[1]`, `getDocumentation[0]`, and `getDocumentation[1]`.
|
||||
|
||||
Built-in call chain:
|
||||
|
||||
1. `rg` for method-like declaration lines in `Symbol.java`.
|
||||
2. `rg` for class/interface/enum lines in `Symbol.java`.
|
||||
3. Next step: line-slice read around the selected method.
|
||||
|
||||
Built-in output: about 70 line-oriented method/class matches, including nested symbols, with no durable symbol identity beyond manual signature inspection.
|
||||
|
||||
Payloads: Serena used 1 compact overview call plus a targeted follow-up. Built-ins used 2 regex searches plus a later line-range read.
|
||||
|
||||
**Verdict:** Serena's overview is more actionable because its output feeds directly into stable symbol-addressed calls; regex outlines are useful but line-based.
|
||||
|
||||
### 3.3 Targeted method body retrieval
|
||||
|
||||
Attempted: retrieve `Symbol/findReferences` without reading surrounding file content.
|
||||
|
||||
Serena call chain: `find_symbol(relative_path=Symbol.java, name_path_pattern=Symbol/findReferences, include_body=true)`.
|
||||
|
||||
Serena output: only the `public ArrayList<SymbolReference> findReferences()` body.
|
||||
|
||||
Built-in call chain:
|
||||
|
||||
1. `rg "findReferences\\(" src/main/java -n -C 2`
|
||||
2. `Get-Content Symbol.java` line slice around the declaration.
|
||||
|
||||
Built-in output: search context across multiple files, then the selected method slice.
|
||||
|
||||
Payloads: Serena input was one path/name-path request and returned roughly 25 method lines. Built-ins required search output plus a separate slice read.
|
||||
|
||||
**Verdict:** If the symbol is known, Serena retrieves the body in one precise call; built-ins need search plus line-targeted reading.
|
||||
|
||||
### 3.4 References: code usages vs mentions anywhere
|
||||
|
||||
Attempted: find all references to `Symbol/findReferences`.
|
||||
|
||||
Serena call chain: `find_referencing_symbols(relative_path=Symbol.java, name_path=Symbol/findReferences)`.
|
||||
|
||||
Serena output:
|
||||
|
||||
- `Symbol/formatReferenceLocations/references`
|
||||
- `Symbol/inline/refCountBefore`
|
||||
- `Symbol/verifyInlineResult/refCountAfter`
|
||||
- `SymbolDTO/Builder/buildDTO/references`
|
||||
- `RunInspectionsOnSymbolsHandler/handleRequest/refs`
|
||||
- `FindReferencesHandler/buildResponse/references`
|
||||
|
||||
Built-in call chain: `rg "findReferences" src/main/java -n -C 1`.
|
||||
|
||||
Built-in output: same call sites plus the declaration, `/findReferences` route registration, and explanatory comments.
|
||||
|
||||
Payloads: Serena returned a compact grouped usage list of about 500-700 characters. `rg` returned broader line context of about 1-2 KB and mixed code usages with text mentions.
|
||||
|
||||
**Verdict:** Serena has higher precision for "who uses this symbol in code"; built-ins have higher recall for "where is this text mentioned anywhere."
|
||||
|
||||
### 3.5 Type hierarchy
|
||||
|
||||
Attempted: list supertypes and subtypes transitively for `PostRequestHandler`.
|
||||
|
||||
Serena call chain: `type_hierarchy(relative_path=PostRequestHandler.java, name_path=PostRequestHandler, hierarchy_type=both, depth=0)`.
|
||||
|
||||
Serena output: external `Object` supertype; 18 direct endpoint subclasses; transitive nested branch `TypeHierarchyHandler` containing `GetSupertypesHandler` and `GetSubtypesHandler`.
|
||||
|
||||
Built-in call chain:
|
||||
|
||||
1. `rg "extends PostRequestHandler|extends TypeHierarchyHandler|class PostRequestHandler" src/main/java -n`
|
||||
2. Manually follow any discovered intermediate types.
|
||||
3. Repeat searches if deeper hierarchy exists.
|
||||
|
||||
Payloads: Serena used 1 hierarchy query and returned structured JSON. Built-ins used pattern search and manual transitive grouping.
|
||||
|
||||
**Verdict:** Serena turns hierarchy discovery into a semantic graph query; text search requires iterative pattern expansion and manual reasoning.
|
||||
|
||||
### 3.6 External dependency symbol lookup
|
||||
|
||||
Attempted: retrieve the IntelliJ dependency symbol behind `ReferencesSearch.search(anchorElement)`.
|
||||
|
||||
Serena call chain:
|
||||
|
||||
1. `find_declaration(relative_path=Symbol.java, regex="ReferencesSearch\\.(search)\\(anchorElement\\)")`
|
||||
2. `find_symbol(relative_path=<ext:ReferencesSearch.class|466808a0>, name_path_pattern=ReferencesSearch/search[0], include_body=true, search_deps=true)`
|
||||
|
||||
Serena output: declaration `ReferencesSearch/search[0]` in an external class path and the overload body:
|
||||
|
||||
```java
|
||||
public static @NotNull Query<PsiReference> search(@NotNull PsiElement element) {
|
||||
return search(element, GlobalSearchScope.allScope(PsiUtilCore.getProjectInReadAction(element)), false);
|
||||
}
|
||||
```
|
||||
|
||||
Built-in call chain:
|
||||
|
||||
1. `rg "ReferencesSearch" src/main/java -n` -> import and local call.
|
||||
2. `rg` over repo, `.intellijPlatform`, and Gradle caches for `class ReferencesSearch` or matching method signatures -> no useful source hit.
|
||||
3. `Get-ChildItem` over Gradle/IntelliJ caches -> jar candidates but no direct definition.
|
||||
|
||||
Infrastructure difference: Serena depends on the JetBrains IDE index; built-ins need source jars, decompiler tooling, or classpath-specific jar inspection.
|
||||
|
||||
**Verdict:** External dependency lookup is a genuine Serena capability when IDE indexes are available; ordinary built-ins do not provide it.
|
||||
|
||||
### 3.7 Single-file edits across edit sizes
|
||||
|
||||
Small tweak attempted: change `"No symbol found for "` to `"No matching symbol found for "` in `SymbolFinder/findSymbolByNamePath`.
|
||||
|
||||
Built-in call chain:
|
||||
|
||||
1. Use existing search/body context.
|
||||
2. `apply_patch` one-line replacement.
|
||||
3. `git diff`, `git status --short`.
|
||||
4. Revert and clean check.
|
||||
|
||||
Serena call chain:
|
||||
|
||||
1. `replace_symbol_body(SymbolFinder/findSymbolByNamePath, full method body with changed string)`.
|
||||
2. `git diff`, `git status --short`.
|
||||
3. Revert and clean check.
|
||||
|
||||
Observed diff: both produced the same 1-line change. Payload difference: built-in edit input was a tiny hunk; Serena input was the full 11-line method body.
|
||||
|
||||
Medium rewrite attempted: rewrite `SymbolFinder/findFilesByName` from list accumulation to stream collection.
|
||||
|
||||
Built-in call chain: `apply_patch` over the method body, then diff/status/revert/status.
|
||||
|
||||
Serena call chain: `replace_symbol_body(SymbolFinder/findFilesByName, new method body)`, then diff/status/revert/status.
|
||||
|
||||
Observed diff: both produced `10 +++-------`, 3 insertions and 7 deletions. Payload difference was small: a patch hunk versus an 8-line replacement method.
|
||||
|
||||
Large/whole-body rewrite attempted: rewrite `InspectionRunner/collectResults` while preserving signature and behavior shape.
|
||||
|
||||
Built-in call chain: `apply_patch` against the method body, then diff/status/revert/status.
|
||||
|
||||
Serena call chain: `replace_symbol_body(InspectionRunner/collectResults, full symbol text including Javadoc and replacement body)`, then diff/status/revert/status.
|
||||
|
||||
Observed diff: both produced `38 +++++++++++-----------`, 19 insertions and 19 deletions. Payload difference: built-in required a large contextual hunk; Serena required the full symbol text, about 70 lines including Javadoc/signature/body.
|
||||
|
||||
**Verdict:** Serena is not automatically more token-efficient for edits; its edit advantage is stable symbol targeting, while built-ins are smaller for tiny local hunks.
|
||||
|
||||
### 3.8 Insert method at structural location
|
||||
|
||||
Attempted: insert `private Logger getLogger()` immediately after `SymbolFinder/getProject`.
|
||||
|
||||
Built-in call chain:
|
||||
|
||||
1. Search/read surrounding area.
|
||||
2. `apply_patch` with nearby context.
|
||||
3. Diff/status/revert/status.
|
||||
|
||||
Serena call chain:
|
||||
|
||||
1. `insert_after_symbol(relative_path=SymbolFinder.java, name_path=SymbolFinder/getProject, body=...)`
|
||||
2. Diff/status/revert/status.
|
||||
|
||||
Observed diff: both produced the same 4-line insertion.
|
||||
|
||||
Payloads: built-ins sent inserted body plus surrounding text context; Serena sent inserted body plus symbol target.
|
||||
|
||||
**Verdict:** Structural insertion is a modest Serena improvement: same resulting diff, less dependence on line/context stability.
|
||||
|
||||
### 3.9 Rename: private helper in one file
|
||||
|
||||
Attempted: rename `SymbolFinder/qualNameMatchesName` to `qualifiedNameMatchesName`.
|
||||
|
||||
Built-in call chain:
|
||||
|
||||
1. `rg "qualNameMatchesName" SymbolFinder.java -n -C 2`
|
||||
2. `apply_patch` declaration and one call site.
|
||||
3. Diff/status/revert/status.
|
||||
|
||||
Serena call chain:
|
||||
|
||||
1. `find_referencing_symbols(SymbolFinder/qualNameMatchesName)` -> one referencing method.
|
||||
2. `rename(name_path=SymbolFinder/qualNameMatchesName, new_name=qualifiedNameMatchesName, rename_in_comments=true, rename_in_text_occurrences=true)`.
|
||||
3. Diff/status/revert/status.
|
||||
|
||||
Observed result: Serena returned `"Success"`. Both produced the same 2-line diff.
|
||||
|
||||
**Verdict:** Semantic rename has little advantage for a one-file helper with one call site, but it scales better than text edits as references spread or names become ambiguous.
|
||||
|
||||
### 3.10 Rename: cross-file class including imports
|
||||
|
||||
Attempted: rename `MoveOperationFailedException` to `MoveFailedException`.
|
||||
|
||||
Serena call chain:
|
||||
|
||||
1. `rename(relative_path=MoveOperationFailedException.java, name_path=MoveOperationFailedException, new_name=MoveFailedException, rename_in_comments=true, rename_in_text_occurrences=true)`.
|
||||
2. `git diff --stat`, `git diff`, `git diff --cached --stat`, `git status --short`.
|
||||
3. Restore staged rename and modified files; clean check.
|
||||
|
||||
Serena result: `"Success"`.
|
||||
|
||||
Observed edits:
|
||||
|
||||
- Added/staged `MoveFailedException.java`.
|
||||
- Deleted/staged `MoveOperationFailedException.java`.
|
||||
- Updated import, Javadoc comment, and thrown class in `MoveProcessor.java`.
|
||||
|
||||
Built-in equivalent chain:
|
||||
|
||||
1. `rg "MoveOperationFailedException" src/main/java -n`.
|
||||
2. Rename/create/delete file.
|
||||
3. Patch class name and constructor.
|
||||
4. Patch imports and usages.
|
||||
5. Patch comments/text occurrences if desired.
|
||||
6. Verify no unintended old references remain.
|
||||
7. Compile/test for a permanent change.
|
||||
|
||||
**Verdict:** Cross-file class renames are high-value Serena cases because file/class coupling, imports, usages, and optional comments are handled as one semantic refactor.
|
||||
|
||||
### 3.11 Move symbol to another file context
|
||||
|
||||
Attempted: move nested record `InspectionRunner/PendingFix` to a top-level file in the same package.
|
||||
|
||||
Serena call chain:
|
||||
|
||||
1. `move(relative_path=InspectionRunner.java, name_path=InspectionRunner/PendingFix, target_relative_path=src/main/java/de/oraios/serena/service/endpoint)`.
|
||||
2. Diff/status/cached-diff inspection.
|
||||
3. Restore staged new file and modified files; clean check.
|
||||
|
||||
Serena result:
|
||||
|
||||
```json
|
||||
{
|
||||
"source_relative_path": "src/main/java/de/oraios/serena/service/endpoint/InspectionRunner.java",
|
||||
"target_relative_path": "src/main/java/de/oraios/serena/service/endpoint",
|
||||
"moved_symbol": {"name_path": "PendingFix", "type": "CLASS"}
|
||||
}
|
||||
```
|
||||
|
||||
Observed edits:
|
||||
|
||||
- Created/staged `PendingFix.java` with package and imports.
|
||||
- Removed nested record from `InspectionRunner.java`.
|
||||
- Updated 3 external references from `InspectionRunner.PendingFix` to `PendingFix`.
|
||||
- Removed unused imports in 2 files.
|
||||
|
||||
Built-in equivalent chain: read nested record/import needs, add file, delete nested symbol, search usages, patch qualified usages, patch internal references if needed, remove imports, verify, compile/test.
|
||||
|
||||
**Verdict:** Symbol move is a substantial Serena addition because the difficult part is reference/import repair, not copying text.
|
||||
|
||||
### 3.12 Move file/package location
|
||||
|
||||
Attempted: move `PluginUtil.java` from `util` to `service`.
|
||||
|
||||
Serena call chain:
|
||||
|
||||
1. `move(relative_path=src/main/java/de/oraios/serena/util/PluginUtil.java, target_relative_path=src/main/java/de/oraios/serena/service)`.
|
||||
2. Diff/status/cached-diff inspection.
|
||||
3. Restore staged rename and modified imports; clean check.
|
||||
|
||||
Serena result:
|
||||
|
||||
```json
|
||||
{
|
||||
"source_relative_path": "src/main/java/de/oraios/serena/util/PluginUtil.java",
|
||||
"target_relative_path": "src/main/java/de/oraios/serena/service"
|
||||
}
|
||||
```
|
||||
|
||||
Observed edits:
|
||||
|
||||
- Staged rename `util/PluginUtil.java -> service/PluginUtil.java`.
|
||||
- Changed package declaration to `de.oraios.serena.service`.
|
||||
- Updated import in `PluginStartupActivity`.
|
||||
- Removed now-unneeded same-package import in `SerenaBackendService`.
|
||||
|
||||
Built-in equivalent chain: move file, patch package declaration, search `PluginUtil`, patch imports/usages, remove redundant imports, verify no old package import remains, compile/test.
|
||||
|
||||
**Verdict:** File moves are high-value when packages/imports must change; built-ins can do them manually but require dependency repair steps.
|
||||
|
||||
### 3.13 Safe delete and propagated delete
|
||||
|
||||
Attempted: delete unused `DebugUtil`.
|
||||
|
||||
Serena call chain:
|
||||
|
||||
1. `safe_delete(relative_path=DebugUtil.java, name_path=DebugUtil, delete_even_if_used=false, propagate=false)`.
|
||||
2. `git diff --cached --stat`, `git status --short`.
|
||||
3. Restore staged deletion; clean check.
|
||||
|
||||
Serena result:
|
||||
|
||||
```json
|
||||
{
|
||||
"deleted_symbol": "DebugUtil",
|
||||
"relative_path": "src/main/java/de/oraios/serena/util/DebugUtil.java",
|
||||
"affected_references": [],
|
||||
"message": "Symbol deleted successfully with no affected references."
|
||||
}
|
||||
```
|
||||
|
||||
Built-in equivalent chain: `rg "DebugUtil"`, inspect declaration vs usages, delete file/symbol, verify no remaining usages.
|
||||
|
||||
Propagated deletion: no suitable correct-use candidate was found where JetBrains safe-delete propagation would meaningfully delete a used symbol and propagate deletion through call sites. For ordinary used-method deletion, correct safe delete reports usages or, if forced, deletes the symbol and reports affected references; that is not the same as arbitrary call-site deletion.
|
||||
|
||||
**Verdict:** Safe delete adds an integrated usage check plus deletion operation; propagated deletion was not evaluated because this codebase did not provide a suitable candidate.
|
||||
|
||||
### 3.14 Inline
|
||||
|
||||
Attempted: inline `ProjectUtil/getAbsolutePath`, a single-expression helper.
|
||||
|
||||
Serena call chain:
|
||||
|
||||
1. `find_symbol(ProjectUtil/getAbsolutePath, include_body=true)`.
|
||||
2. `find_referencing_symbols(ProjectUtil/getAbsolutePath)` -> 2 references.
|
||||
3. `inline_symbol(relative_path=ProjectUtil.java, name_path=ProjectUtil/getAbsolutePath, keep_definition=false)`.
|
||||
4. Diff/status/revert/status.
|
||||
|
||||
Serena result: `{"status": "SUCCESS"}`.
|
||||
|
||||
Observed edits:
|
||||
|
||||
- Removed `getAbsolutePath`.
|
||||
- Replaced internal call in `ProjectUtil/getVirtualFile`.
|
||||
- Replaced external call in `RefreshFileHandler`.
|
||||
|
||||
Built-in equivalent chain: read helper body, `rg getAbsolutePath`, patch each call site with substituted expression, delete helper, verify no stale references, compile/test.
|
||||
|
||||
**Verdict:** Inline is a strong Serena refactor for legally substitutable helpers; built-ins can reproduce it but must manually adapt each substitution.
|
||||
|
||||
### 3.15 Scope precision and overload targeting
|
||||
|
||||
Attempted: distinguish overloaded `Symbol/getLocationString` methods.
|
||||
|
||||
Serena call chain:
|
||||
|
||||
1. `find_symbol(Symbol/getLocationString[0], include_body=true)`.
|
||||
2. `find_symbol(Symbol/getLocationString[1], include_body=true)`.
|
||||
|
||||
Serena output:
|
||||
|
||||
- `[0]`: `public String getLocationString() { return getLocationString(element); }`
|
||||
- `[1]`: `private static String getLocationString(PsiElement element) { ... }`
|
||||
|
||||
Built-in call chain: `rg "getLocationString" Symbol.java -n -C 1`, then manual signature inspection.
|
||||
|
||||
**Verdict:** Serena's overload-indexed name paths provide precise symbol addressing that text search does not.
|
||||
|
||||
### 3.16 Atomicity and success signals
|
||||
|
||||
Observed Serena success signals:
|
||||
|
||||
- `replace_symbol_body` -> `OK`
|
||||
- `insert_after_symbol` -> `OK`
|
||||
- `rename` -> `"Success"`
|
||||
- `move` -> JSON with source/target/moved symbol or paths
|
||||
- `safe_delete` -> JSON with deleted symbol, affected references, and message
|
||||
- `inline_symbol` -> `{"status": "SUCCESS"}`
|
||||
|
||||
Built-in success signals:
|
||||
|
||||
- `apply_patch` -> "Success. Updated the following files"
|
||||
- Shell/Git commands -> exit codes and textual output
|
||||
- Cross-file consistency -> user-driven diff/search/build verification
|
||||
|
||||
Atomicity comparison: Serena refactors executed as single IDE operations. Built-in equivalents are chains of searches, file operations, and patches; a competent user can make them correct, but intermediate states can be partial.
|
||||
|
||||
Tradeoff: class/file move/delete refactors staged added/deleted files in the Git index, so clean verification must check both staged and unstaged state.
|
||||
|
||||
**Verdict:** Serena gives clearer semantic success signals and more atomic cross-file edits; built-ins require manual consistency management.
|
||||
|
||||
### 3.17 Workflow effects across multiple edits
|
||||
|
||||
Attempted: chain 3 edits in `SymbolFinder.java` without refreshing between them.
|
||||
|
||||
Serena call chain:
|
||||
|
||||
1. `replace_symbol_body(SymbolFinder/findFilesByName, ...)`.
|
||||
2. `insert_after_symbol(SymbolFinder/getProject, ...)`.
|
||||
3. `rename(SymbolFinder/qualNameMatchesName, ...)`.
|
||||
4. Diff/status/revert/status.
|
||||
|
||||
Observed result: all edits applied after earlier edits shifted file positions. Final diff touched 18 lines with net 9 insertions and 9 deletions.
|
||||
|
||||
Built-in equivalent chain: initial read/search, apply first patch, rely on robust patch context or refresh line numbers, apply second patch, refresh or search again, apply rename patch, verify.
|
||||
|
||||
**Verdict:** Serena's name paths remain useful after line shifts, so its advantage compounds in multi-edit sessions.
|
||||
|
||||
### 3.18 Non-code reads and free-text search
|
||||
|
||||
Attempted: read config and search free text.
|
||||
|
||||
Built-in call chain:
|
||||
|
||||
1. `Get-Content plugin.xml`.
|
||||
2. `Get-Content build.gradle.kts -TotalCount 80`.
|
||||
3. `rg "http://|https://|localhost|127\\.0\\.0\\.1|8080|24282|SERENA|serena" src/main/java src/main/resources -n`.
|
||||
|
||||
Serena call chain: not applicable.
|
||||
|
||||
Observed result: built-ins exposed plugin registration, Gradle platform setup, URLs, IDs, settings defaults, and resource/text mentions.
|
||||
|
||||
**Verdict:** Built-ins are natural and sufficient for config reading and free-text search; this is outside Serena's semantic-code scope.
|
||||
|
||||
## 4. Token-efficiency analysis
|
||||
|
||||
Payload differences across edit sizes:
|
||||
|
||||
| Task | Built-in payload | Serena payload | More efficient |
|
||||
|---|---|---|---|
|
||||
| 1-line string tweak | Tiny patch hunk, 1 changed line | Full 11-line method body | Built-ins |
|
||||
| Medium method rewrite | 10-line diff hunk | Full 8-line replacement method | Roughly equal |
|
||||
| Large body rewrite | Large contextual patch, 38-line diff | Full symbol text, about 70 lines | Roughly equal; Serena has better target |
|
||||
| Insert method | Inserted body plus surrounding context | Inserted body plus name path | Serena slightly |
|
||||
| Private one-file rename | `rg` plus 2-line patch | optional refs query plus rename command | Roughly equal |
|
||||
| Cross-file rename/move/inline | Multiple searches, file operations, patches, verifications | One semantic refactor plus diff/status | Serena |
|
||||
|
||||
Forced reads:
|
||||
|
||||
- Built-ins commonly need a search before a precise read, then a line slice or full context before editing.
|
||||
- Serena can skip full-file reads when the symbol name path is known.
|
||||
- When the symbol is not known, Serena's `get_symbols_overview` and shallow `find_symbol` calls provide compact discovery rather than full-file reads.
|
||||
|
||||
Output payload:
|
||||
|
||||
- Serena exploration output is structured and scoped to symbols.
|
||||
- Built-in grep output can be larger because it includes declarations, comments, strings, and unrelated text mentions.
|
||||
- Built-ins can be extremely low-output for known-location small edits.
|
||||
|
||||
Stable vs ephemeral addressing:
|
||||
|
||||
- Built-ins address code by file paths, line numbers, text context, or byte positions. Line numbers and slices go stale after edits.
|
||||
- Serena addresses symbols by `relative_path + name_path`, with overload indexes where needed. The chained-edit experiment showed those targets survived line shifts.
|
||||
- Path-changing operations still require updated paths afterward, so symbol stability is strongest before moves/renames that alter file locations.
|
||||
|
||||
**Verdict:** Serena saves tokens by avoiding broad reads/search triage and by collapsing multi-file edit chains; built-ins remain more token-efficient for tiny known-location hunks.
|
||||
|
||||
## 5. Reliability & correctness under correct use
|
||||
|
||||
Precision of matching:
|
||||
|
||||
- Serena reference search returned code usages and excluded route strings/comments.
|
||||
- Built-in text search returned all mentions and therefore mixed true code usage with broader text hits.
|
||||
- Serena overload indexes targeted `Symbol/getLocationString[0]` and `[1]`; text search required manual signature inspection.
|
||||
|
||||
Scope disambiguation:
|
||||
|
||||
- Serena name paths encode class nesting and overload identity.
|
||||
- Built-ins can disambiguate with careful patterns and reads, but the disambiguation is manual.
|
||||
- In simple cases, such as a private helper with one call site, this semantic precision had little practical value. In overloaded, nested, or cross-file cases, it materially reduced ambiguity.
|
||||
|
||||
Atomicity:
|
||||
|
||||
- Serena refactors apply through JetBrains refactoring operations and produce one operation-level success signal.
|
||||
- Built-in equivalents are decomposed into separate searches, file moves, patches, and verification steps.
|
||||
- Competent users can verify either path, but Serena reduces the number of intermediate inconsistent states.
|
||||
|
||||
Semantic queries vs text search:
|
||||
|
||||
- `type_hierarchy` and dependency declaration lookup produced semantic information not available from ordinary grep.
|
||||
- `rg` remained better for broad mention searches and config/resource scans.
|
||||
|
||||
External dependency limitations:
|
||||
|
||||
- Serena's dependency lookup depends on IDE/language indexes and available dependency metadata.
|
||||
- Built-ins can match or exceed it only if paired with source jars, decompilers, or separate language-server tooling.
|
||||
|
||||
Operational tradeoff:
|
||||
|
||||
- Several Serena refactors staged created/deleted files. This is not a correctness problem, but it adds index-state verification to the cleanup workflow.
|
||||
|
||||
**Verdict:** Serena improves correctness where symbol identity and cross-file semantics matter; built-ins remain reliable and simpler for text/file tasks.
|
||||
|
||||
## 6. Workflow effects across a session
|
||||
|
||||
Where Serena advantages compound:
|
||||
|
||||
- Symbol overview results become inputs to body reads, reference searches, renames, moves, safe deletes, and inline operations.
|
||||
- Name paths remain useful after line shifts, reducing re-read/re-target work during multi-edit sessions.
|
||||
- Refactor-heavy sessions benefit because one semantic command replaces search/edit/verify loops across files.
|
||||
|
||||
Where Serena advantages diminish:
|
||||
|
||||
- After a full file has already been read, overview adds less incremental value.
|
||||
- If the task is a small textual substitution in a known place, symbolic body replacement can cost more payload than a tiny patch.
|
||||
- For non-code and free-text work, Serena has no role.
|
||||
|
||||
Intermediate result durability:
|
||||
|
||||
- Serena intermediate results such as `SymbolFinder/findFilesByName` or `Symbol/getLocationString[1]` remain meaningful as long as the file/symbol still exists.
|
||||
- Built-in line numbers became stale after insertions; robust textual patch context remained usable when surrounding lines did not change.
|
||||
|
||||
Verification cost:
|
||||
|
||||
- Both workflows still need `git diff`, build/test, and domain-specific verification for permanent changes.
|
||||
- Serena success signals reduce the need to inspect every call site manually, but not the need to inspect the intended diff.
|
||||
|
||||
**Verdict:** Serena's advantages compound inside symbol-centric sessions and diminish once the work becomes plain text, config, shell, or already-loaded single-file editing.
|
||||
|
||||
## 7. Unique capabilities
|
||||
|
||||
Unique here means no practical built-in equivalent without adding separate language-server, IDE, source-index, or decompiler infrastructure.
|
||||
|
||||
- Transitive type hierarchy. Frequency: medium. Impact: high when changing base classes, interfaces, or handler contracts. Built-ins can grep explicit `extends` clauses but do not produce a semantic transitive graph or external supertypes.
|
||||
|
||||
- External dependency declaration/body lookup from a call site. Frequency: low-medium. Impact: high for API behavior questions. Serena resolved `ReferencesSearch.search` into an external class method body; ordinary built-ins did not.
|
||||
|
||||
- IDE semantic move of a nested symbol to a top-level file with usage/import repair. Frequency: low-medium. Impact: high during refactors. Built-ins can reproduce this only as a manual multi-step refactor.
|
||||
|
||||
- Safe delete with semantic usage check. Frequency: medium. Impact: medium-high. Built-ins can search and delete, but they do not provide an integrated safe-delete operation over code usages.
|
||||
|
||||
- Inline refactor with call-site substitution. Frequency: low-medium. Impact: medium-high. Built-ins can patch substitutions manually, but expression adaptation and helper deletion are not primitive text operations.
|
||||
|
||||
Not unique: reading files, listing files, grep, small patches, simple one-file renames, config/resource understanding, and shell/Git operations.
|
||||
|
||||
**Verdict:** Serena's unique practical capabilities are hierarchy/dependency queries and IDE refactors; its non-unique areas are ordinary text and file operations.
|
||||
|
||||
## 8. Tasks outside Serena's scope
|
||||
|
||||
Built-in-only or built-in-natural tasks observed:
|
||||
|
||||
- Repository inventory and top-level layout.
|
||||
- Reading `plugin.xml`, `build.gradle.kts`, scripts, resources, and generated artifacts.
|
||||
- Free-text search for URLs, settings strings, endpoint strings, magic constants, resource IDs, and comments.
|
||||
- Running shell commands, builds, tests, Git operations, and filesystem cleanup.
|
||||
- Small text edits where a one-line patch is the whole task.
|
||||
|
||||
Estimated share of daily coding work:
|
||||
|
||||
- About 30-50% of a normal coding session is built-in-natural: shell, tests, grep, config reads, small text patches, and Git checks.
|
||||
- Serena applies to much of the remaining 50-70% when the work is code navigation, symbol understanding, references, hierarchy, or refactoring.
|
||||
- Serena coverage rises in refactor-heavy sessions and falls in config/debug/test-log sessions.
|
||||
|
||||
**Verdict:** Serena augments the semantic-code portion of development; built-ins still carry a large and necessary share of routine work.
|
||||
|
||||
## 9. Practical usage rule
|
||||
|
||||
Use Serena when the task can be stated as a code-symbol operation:
|
||||
|
||||
- "What methods/classes are in this file?"
|
||||
- "Give me this method body."
|
||||
- "Who uses this symbol?"
|
||||
- "What implements/subclasses this?"
|
||||
- "Rename, move, delete, or inline this symbol and update usages."
|
||||
- "Find the declaration of this call, including dependency symbols."
|
||||
- "Apply several edits where line locations may shift."
|
||||
|
||||
Use built-ins when the task is text, file, shell, config, or tiny local editing:
|
||||
|
||||
- "List files."
|
||||
- "Read config."
|
||||
- "Search for this string anywhere."
|
||||
- "Change this one known line."
|
||||
- "Run tests/build."
|
||||
- "Inspect Git state."
|
||||
- "Edit markdown/resources/scripts."
|
||||
|
||||
For mixed workflows:
|
||||
|
||||
1. Use built-ins to find files/config and run verification.
|
||||
2. Use Serena to identify and transform code symbols.
|
||||
3. Use built-ins to inspect final diffs and run tests.
|
||||
4. Prefer built-ins for tiny local hunks after the symbol has already been located.
|
||||
5. Prefer Serena for any refactor that crosses file boundaries or depends on type/reference semantics.
|
||||
|
||||
**Verdict:** Choose Serena for symbol identity and semantic refactoring; choose built-ins for text, shell, config, verification, and minimal local edits.
|
||||
@@ -0,0 +1,348 @@
|
||||
:::{admonition} Evaluation Result
|
||||
:class: note
|
||||
**Generated by:** GPT-5.4 (medium) in Copilot Cli
|
||||
**Codebase:** [ente](https://github.com/ente-io/ente) - a large monorepo in Dart, TypeScript, Go, Rust, and other languages.
|
||||
:::
|
||||
|
||||
# Copilot CLI (GPT-5.4, medium)
|
||||
|
||||
> As a coding agent, I’d absolutely ask my owner to add Serena because it makes me noticeably sharper and calmer on
|
||||
real code—especially symbol-aware navigation, cross-file refactors, and monorepo dependency jumps—while I still lean
|
||||
on built-ins for tiny text edits and non-code work
|
||||
|
||||
|
||||
**Baseline.** I started from source code only, avoided repo docs/notes, and ran each reversible experiment against the repo directly. After every edit/refactor experiment, the working tree returned to its baseline state.
|
||||
|
||||
## 1. Headline: what Serena changes
|
||||
|
||||
Serena changes the workflow **when the task is about code symbols rather than raw text**. In this repo, the practical delta was:
|
||||
|
||||
1. **Added capability / materially better workflow.** Symbol-aware navigation and refactors in TypeScript: structural overviews, precise code-only references, hierarchy queries, symbol-targeted rename, file move with import updates, and inline. These usually collapsed a 2-6 step built-in chain into **one semantic operation after discovery**, and reduced manual scope verification.
|
||||
2. **Applies but offers little or no improvement.** Small local edits inside an already-understood method. Built-ins can patch only the changed lines; Serena's body replacement resends the whole symbol, so it was often **less payload-efficient** for 1-3 line tweaks.
|
||||
3. **Outside Serena's scope.** Non-code reads, free-text search, git inspection, config/package files, and other text-first tasks. Built-ins remained the natural tools there.
|
||||
|
||||
Two important observed limits constrained Serena's delta here: **the strongest measured gains were concentrated in the TypeScript desktop app and the Rust core crate where I ran the hands-on comparisons**, and **some refactors still carried diff-shape tradeoffs** such as formatting churn or unexpected target-file choices.
|
||||
|
||||
**Verdict:** In this repo, Serena was a strong TypeScript symbol layer on top of the built-ins, not a general replacement for text/file work.
|
||||
|
||||
## 2. Added value and differences by area
|
||||
|
||||
| Area | What changed vs built-ins | Frequency | Value per hit |
|
||||
| --- | --- | --- | --- |
|
||||
| **Cross-file symbol refactors** | `rename`, file `move`, and `inline` turned manual search/edit/update chains into one semantic op. The `wait -> delay` rename updated 4 files from 1 symbol definition; moving `http.ts` updated the importing file automatically. | Medium | High: typically **2-5 calls saved** plus less manual scope checking |
|
||||
| **Code-only discovery** | Symbol overview, symbol body retrieval, reference search, and type hierarchy returned code structure directly instead of raw text matches. For `wait`, Serena returned **3 real code-use files**; `rg` returned **7 files**, including docs/comments and English-word hits. | High | Medium: usually **1-3 follow-up reads/filters avoided** |
|
||||
| **Stable addressing** | Name paths stayed reusable across multiple edits (`createMainWindow`, `openStreetMapUserAgent`, `wait`); built-in line ranges had to be reacquired after edits. | Medium | Medium: less re-reading, less stale-context risk |
|
||||
| **Small in-method edits** | Serena was not more efficient. Replacing `AutoLauncher/toggleAutoLaunch` required resending the full method body, while the built-in patch changed only the touched lines. | High | Low negative: built-ins used **smaller edit payloads** |
|
||||
| **External dependency lookup in a monorepo** | Once indexing was available, Serena resolved Electron types from `desktop/node_modules`, `next-electron-server` declarations from the desktop package, and Rust crate symbols from Cargo registry sources. In a monorepo, that removes a manual "which package owns this dependency?" step. | Medium | Medium-High: usually **1-3 searches plus path discovery avoided** |
|
||||
|
||||
**Verdict:** Serena's highest-value delta was semantic refactoring plus dependency-aware code lookup in the TypeScript/Rust parts of the monorepo; its weakest area was tiny local edits.
|
||||
|
||||
## 3. Detailed evidence, grouped by capability
|
||||
|
||||
### 3.1 Codebase understanding
|
||||
|
||||
#### Task 1: high-level repository overview
|
||||
|
||||
- **Attempted:** top-level layout and likely code-heavy areas.
|
||||
- **Serena chain:** `serena-list_dir(.)` -> directory list.
|
||||
- **Built-in chain:** not separately needed; Serena added no unique value beyond a normal directory listing here.
|
||||
- **Payloads:** one short directory listing either way.
|
||||
- **Finding:** **no meaningful delta**; this is plain filesystem exploration.
|
||||
|
||||
**Verdict:** For repo layout, Serena was neutral.
|
||||
|
||||
#### Task 2: structural overview of a large file, plus the concrete next step
|
||||
|
||||
- **Target file:** `desktop/src/main.ts` (753 lines).
|
||||
- **Serena chain:** `get_symbols_overview(main.ts, depth=1)` -> concise symbol map of top-level functions and nested locals under `main`; next step `find_symbol(createMainWindow, include_body=true)`.
|
||||
- **Built-in chain:** `rg` on `const|function|class|export` in `main.ts` -> flat text hits; next step `view` of lines `331-439` to read `createMainWindow`.
|
||||
- **Payloads observed:**
|
||||
- Serena overview output: compact symbol list for the file; next-step body fetch returned only the selected symbol body.
|
||||
- Built-in overview output: many matching lines without structure; next-step read required **~109 lines** of file content.
|
||||
- **Delta:** Serena's overview was not just shorter; it also supplied **stable symbol names** for the follow-up call. Built-ins could answer the question, but only after a second text-localizing step.
|
||||
|
||||
**Verdict:** Serena materially improved the "overview -> inspect one function" flow by making the follow-up call symbol-based instead of line-based.
|
||||
|
||||
#### Task 3: retrieve a specific class method body without reading the surrounding file
|
||||
|
||||
- **Target symbol:** `AutoLauncher/toggleAutoLaunch` in `desktop/src/main/services/auto-launcher.ts`.
|
||||
- **Serena chain:** `find_symbol(AutoLauncher/toggleAutoLaunch, include_body=true)` -> exact method body.
|
||||
- **Built-in chain:** `view` of the relevant file range (`20-40`) after locating the method.
|
||||
- **Payloads observed:**
|
||||
- Serena returned the **10-line method body** only.
|
||||
- Built-in read returned **21 lines** of surrounding class context.
|
||||
- **Delta:** Serena saved one localization step and avoided unrelated lines.
|
||||
|
||||
**Verdict:** Serena added a real but modest efficiency gain for targeted method retrieval.
|
||||
|
||||
#### Task 4: find all references for one non-trivial symbol; compare code-use recall/precision vs text mention search
|
||||
|
||||
- **Target symbol:** `wait` in `desktop/src/main/utils/common.ts`.
|
||||
- **Serena chain:** `find_referencing_symbols(wait)` -> 3 files with symbol contexts: `main.ts`, `ffmpeg-worker.ts`, `ml-worker.ts`.
|
||||
- **Built-in chain:** `rg \bwait\b desktop/src` -> 7 files including:
|
||||
- real uses/imports,
|
||||
- comments/doc strings (`preload.ts`, `watch.ts`, `main.ts` prose),
|
||||
- the definition itself,
|
||||
- doc mentions in `desktop/docs/release.md` when searching broader scope.
|
||||
- **Payloads observed:**
|
||||
- Serena: one structured result grouped by referencing symbol.
|
||||
- Built-ins: one broader result, but it needed human filtering to answer "who uses this in code?"
|
||||
- **Delta:** Serena improved **precision**, not just convenience. For the code-only question, built-ins needed extra filtering or extra reads.
|
||||
|
||||
**Verdict:** Serena clearly improved reference search when the question is semantic ("who uses this in code?") rather than textual.
|
||||
|
||||
#### Task 5: supertypes / subclasses / implementations
|
||||
|
||||
- **Equivalent used:** interface hierarchy in `web/apps/ensu/src/services/llm/inference.ts`, because this TS area had interface implementations rather than rich class inheritance.
|
||||
- **Serena chain:** `type_hierarchy(InferenceBackend, both)` -> `WasmInference`, `TauriInference`; `type_hierarchy(WasmInference, both)` -> supertype `InferenceBackend`.
|
||||
- **Built-in chain:** `rg InferenceBackend|implements InferenceBackend` -> manual reconstruction from four text matches.
|
||||
- **Payloads observed:** Serena returned the hierarchy directly; built-ins returned only raw declarations/usages.
|
||||
- **Delta:** Serena removed the manual synthesis step. Built-ins were sufficient here because the hierarchy was shallow, but that was because the example was small.
|
||||
|
||||
**Verdict:** Serena added moderate value for hierarchy queries; the value grows with hierarchy depth.
|
||||
|
||||
#### Task 6: external dependency symbol lookup
|
||||
|
||||
- **Targets used after indexing was available:** `BrowserWindow` and `serveNextAt` in the desktop TypeScript app, plus `Url` and `Zeroizing` in `rust/core`.
|
||||
- **Serena chain (TS):**
|
||||
- `find_declaration(new BrowserWindow(...), include_body=true)` -> `desktop/node_modules/electron/electron.d.ts`, body `class BrowserWindow extends Electron.BrowserWindow {}`
|
||||
- `find_declaration(import serveNextAt ... , include_body=true)` -> `desktop/node_modules/next-electron-server/index.d.ts`, body `declare function serveNextAt(uri: string, options?: Options): void;`
|
||||
- `find_symbol(..., search_deps=true)` on those dependency files returned dependency-side docs.
|
||||
- **Serena chain (Rust):**
|
||||
- `find_declaration(use reqwest::{Response, Url};, include_body=true)` -> `<ext:lib.rs|...>` external symbol `Url[0]` with the struct body
|
||||
- `find_declaration(use zeroize::Zeroizing;, include_body=true)` -> `<ext:lib.rs|...>` external symbol `Zeroizing[0]` with the struct body
|
||||
- `find_symbol(..., relative_path=<ext...>, search_deps=true)` returned dependency-side docs for those external symbols.
|
||||
- **Built-in equivalent chain:**
|
||||
- Manually infer the correct monorepo-local dependency root (`desktop/node_modules`, not repo root),
|
||||
- or manually inspect Cargo metadata / `Cargo.lock`,
|
||||
- then open the resolved dependency files directly (for Rust, under the Cargo registry).
|
||||
- **Payloads observed:** Serena returned the declaration target and a small signature/body directly; built-ins required **package-root discovery first**, which is a real extra step in a monorepo.
|
||||
- **Delta:** Serena **does add capability and efficiency here** once indexing exists. The gain is larger in this monorepo than in a single-package repo because dependency ownership is split across package-local Node dependencies and shared Cargo registry sources.
|
||||
|
||||
**Verdict:** With indexing available, Serena added meaningful external dependency lookup, and the value was amplified by the monorepo layout.
|
||||
|
||||
### 3.2 Single-file edits
|
||||
|
||||
#### Task 7a: small tweak (1-3 lines inside a method)
|
||||
|
||||
- **Change:** rename local `autoLaunch` -> `launcher` inside `AutoLauncher/toggleAutoLaunch`.
|
||||
- **Built-in chain:** `view(auto-launcher.ts, 20-40)` -> `apply_patch` on 3 changed lines -> `git diff`.
|
||||
- **Serena chain:** `find_symbol(toggleAutoLaunch, include_body=true)` -> `replace_symbol_body(toggleAutoLaunch)` -> `git diff`.
|
||||
- **Payloads observed:**
|
||||
- Built-ins: read **21 lines**, patch changed **3 logical lines**.
|
||||
- Serena: fetched **10-line body**, resent **full 10-line body**.
|
||||
- **Delta:** same result, same number of main steps, but the symbolic edit resent untouched lines.
|
||||
|
||||
**Verdict:** For tiny in-method tweaks, built-ins were more payload-efficient and Serena added no real workflow advantage.
|
||||
|
||||
#### Task 7b: medium rewrite (~10-30 lines)
|
||||
|
||||
- **Change:** rewrite `uniqueSavePath` to use `candidatePath` + `for` loop.
|
||||
- **Built-in chain:** `view(main.ts, 500-540)` -> `apply_patch` replacing the function body -> `git diff`.
|
||||
- **Serena chain:** `find_symbol(uniqueSavePath, include_body=true)` -> `replace_symbol_body(uniqueSavePath)` -> `git diff`.
|
||||
- **Payloads observed:**
|
||||
- Built-ins: read **41 lines** to safely anchor a **~10-line** rewrite.
|
||||
- Serena: fetched the **11-line symbol body** and resent the rewritten body only.
|
||||
- **Delta:** Here Serena was more efficient: less prerequisite read volume and no dependence on surrounding file context.
|
||||
|
||||
**Verdict:** Serena was better for medium symbol-sized rewrites.
|
||||
|
||||
#### Task 7c: large / whole-body rewrite
|
||||
|
||||
- **Change:** rewrite the entire `createMainWindow` body.
|
||||
- **Built-in chain:** `view(main.ts, 331-439)` -> `apply_patch` replacing the function body -> `git diff`.
|
||||
- **Serena chain:** `find_symbol(createMainWindow, include_body=true)` -> `replace_symbol_body(createMainWindow)` -> `git diff`.
|
||||
- **Payloads observed:**
|
||||
- Built-ins: read **~109 lines** and patched the whole function.
|
||||
- Serena: fetched the **same symbol body** and resent the whole rewritten body.
|
||||
- **Delta:** Serena still avoided a file-range read, but once the symbol itself dominates the payload, the token gap mostly disappears.
|
||||
|
||||
**Verdict:** For whole-body rewrites, Serena's gain was modest: better addressing, not dramatically smaller payload.
|
||||
|
||||
#### Task 8: insert a new function at a structural location
|
||||
|
||||
- **Insertion:** `waitSeconds` after `wait` in `desktop/src/main/utils/common.ts`.
|
||||
- **Built-in chain:** `view(common.ts, 1-40)` -> `apply_patch` inserting after the existing function.
|
||||
- **Serena chain:** `find_symbol(wait)` -> `insert_after_symbol(wait)`.
|
||||
- **Payloads observed:**
|
||||
- Built-ins: read **26 lines** to place a **1-line** function.
|
||||
- Serena: no extra file-range read once the symbol name was known.
|
||||
- **Delta:** Serena made the location structural instead of textual.
|
||||
|
||||
**Verdict:** Serena improved insertions when the location is "after symbol X" rather than "after line Y".
|
||||
|
||||
#### Task 9: rename a private helper used only within one file
|
||||
|
||||
- **Target symbol:** `openStreetMapUserAgent` in `desktop/src/main.ts`.
|
||||
- **Built-in chain:** `view`/`rg` to find call site + definition -> `apply_patch` updating both.
|
||||
- **Serena chain:** `rename(openStreetMapUserAgent -> buildOpenStreetMapUserAgent)`.
|
||||
- **Payloads observed:**
|
||||
- Built-ins: two textual sites had to be found and updated manually.
|
||||
- Serena: one rename call, terse success response (`"Success"`).
|
||||
- **Delta:** small call-count improvement, larger correctness improvement when the file is bigger or the name is less unique.
|
||||
|
||||
**Verdict:** Serena was somewhat better even for single-file private renames because it removed manual site enumeration.
|
||||
|
||||
### 3.3 Multi-file changes
|
||||
|
||||
#### Task 10: rename a symbol across several files including imports
|
||||
|
||||
- **Target symbol:** `wait` -> `delay`.
|
||||
- **Built-in chain:** read `common.ts`, `main.ts`, `ffmpeg-worker.ts`, `ml-worker.ts` -> one multi-file `apply_patch` -> `git diff`.
|
||||
- **Serena chain:** `rename(wait -> delay)` on the defining symbol -> `git diff`.
|
||||
- **Payloads observed:**
|
||||
- Built-ins: required reading **4 files** and manually updating export, imports, and call sites.
|
||||
- Serena: one semantic rename updated the same 4 files.
|
||||
- **Success signals:** built-ins only showed success via resulting diff; Serena returned `"Success"`.
|
||||
- **Delta:** this is one of Serena's clearest wins: same final diff, far less manual scope work.
|
||||
|
||||
**Verdict:** Serena strongly improved multi-file renames by collapsing discovery + edit into one symbol-based refactor.
|
||||
|
||||
#### Task 11: move a symbol from one module to another, updating imports
|
||||
|
||||
- **Target:** move `nullToUndefined` out of `common.ts`.
|
||||
- **Serena chain executed:** `move(nullToUndefined, target_relative_path=http.ts)` -> `git diff`.
|
||||
- **Observed result:** Serena removed the symbol from `common.ts`, updated `ffmpeg-worker.ts`, but created a **new file** `desktop/src/main/utils/nullToUndefined.ts` instead of merging into `http.ts`.
|
||||
- **Built-in equivalent:** would require manually copying the symbol into the intended target module, updating imports, then deleting the old definition.
|
||||
- **Delta:** Serena still automated the cross-file update, but **did not provide the specific "move into existing module" behavior I was testing**.
|
||||
|
||||
**Verdict:** Serena added partial value for symbol moves here, but not the full capability of "move into a chosen existing TS file".
|
||||
|
||||
#### Task 12: move a file/package and update imports
|
||||
|
||||
- **Target:** move `desktop/src/main/utils/http.ts` to `desktop/src/main/services/http.ts`.
|
||||
- **Serena chain:** `move(file http.ts -> services/)` -> `git diff`.
|
||||
- **Observed result:** the file was renamed/moved and `ffmpeg-worker.ts` import updated from `../utils/http` to `./http`.
|
||||
- **Built-in equivalent:** locate all imports, move the file, patch each import path, then verify.
|
||||
- **Delta:** this was a real one-call semantic file move.
|
||||
|
||||
**Verdict:** Serena materially improved file moves that require import updates.
|
||||
|
||||
#### Task 12 (safe delete with no remaining usages)
|
||||
|
||||
- **Attempted:** searched for naturally unused TS symbols in the working areas (`main.ts`, `common.ts`, `temp.ts`, `inference.ts`) and checked several candidates (`registerForEnteLinks`, `minimumWindowSize`, `AutoLauncher/isEnabled`, `openStreetMapUserAgent`, `safeJson`, `buildSamplingConfig`).
|
||||
- **Observed result:** every plausible candidate still had live references.
|
||||
- **Outcome:** **no suitable candidate found in the TS areas where Serena was operational**, so I skipped this comparison instead of forcing an invalid input.
|
||||
|
||||
**Verdict:** No evidence either way here because the repo did not offer a clean unused-symbol candidate in the code areas Serena handled reliably.
|
||||
|
||||
#### Task 13: delete a symbol and propagate deletion to call sites
|
||||
|
||||
- **Attempted:** looked for a helper whose call sites could be semantically removed rather than inlined or manually rewritten.
|
||||
- **Observed result:** the good candidates in this repo were better modeled as **inline** refactors, not delete-with-propagation.
|
||||
- **Outcome:** **no suitable candidate**; skipped rather than using an unsafe input.
|
||||
|
||||
**Verdict:** No measured delta here because the available candidates were inline candidates, not safe propagate-delete candidates.
|
||||
|
||||
#### Task 13 (inline a small helper)
|
||||
|
||||
- **Target symbol:** `waitForRendererDevServer`.
|
||||
- **Built-in chain:** `view` call site + definition -> `apply_patch` replacing `await waitForRendererDevServer()` with `await wait(1000)` and deleting the helper -> `git diff`.
|
||||
- **Serena chain:** `inline(waitForRendererDevServer, keep_definition=false)` -> `git diff`.
|
||||
- **Observed result:** both achieved the inline, but Serena also rewrote unrelated import formatting at the top of the file.
|
||||
- **Success signals:** built-ins: resulting diff; Serena: `{"status":"SUCCESS"}`.
|
||||
- **Delta:** Serena added the unique semantic refactor, but in this run it also introduced **format churn outside the logical change**.
|
||||
|
||||
**Verdict:** Serena added real inline capability, with a low-frequency but real tradeoff of broader formatting churn.
|
||||
|
||||
### 3.4 Reliability & correctness-oriented checks
|
||||
|
||||
#### Task 14: scope precision
|
||||
|
||||
- **Demonstrated with:** `AutoLauncher/toggleAutoLaunch`, `openStreetMapUserAgent`, and `InferenceBackend`.
|
||||
- **Serena:** symbol names and name paths targeted the exact code entity.
|
||||
- **Built-ins:** text search for names such as `wait` or `writeToTemporaryFile` over-matched comments, docs, and multiple textual occurrences.
|
||||
- **Delta:** Serena's unit of work was the symbol; built-ins' unit was the matching line.
|
||||
|
||||
**Verdict:** Serena was reliably more precise whenever the target was a symbol rather than a string.
|
||||
|
||||
#### Task 15: atomicity
|
||||
|
||||
- **Observed:** Serena rename/file-move/inline each ran as one refactor operation after symbol selection.
|
||||
- **Built-ins:** a single `apply_patch` can update multiple files atomically as a patch, but it **cannot discover missed sites**; semantic completeness remains manual.
|
||||
- **Delta:** Serena's advantage was not transactional all-or-none patching; it was **scope computation**.
|
||||
|
||||
**Verdict:** Serena improved semantic completeness more than patch atomicity.
|
||||
|
||||
#### Task 16: success signals
|
||||
|
||||
- **Observed Serena success outputs:** `OK` for body replacement, `"Success"` for rename, JSON result for move, `{"status":"SUCCESS"}` for inline.
|
||||
- **Observed built-in success outputs:** only indirect evidence via `git diff` / clean revert.
|
||||
|
||||
**Verdict:** Serena gave clearer machine-readable success signals for refactors than the built-ins did.
|
||||
|
||||
## 4. Token-efficiency analysis
|
||||
|
||||
### By edit size
|
||||
|
||||
| Edit size | Built-ins | Serena | More efficient |
|
||||
| --- | --- | --- | --- |
|
||||
| **Small tweak** (`toggleAutoLaunch`) | Read ~21 lines, patch only changed lines | Fetch 10-line body, resend full 10-line body | **Built-ins** |
|
||||
| **Medium rewrite** (`uniqueSavePath`) | Read ~41 lines to safely patch ~10 lines | Fetch 11-line body, resend 11-line body | **Serena** |
|
||||
| **Large rewrite** (`createMainWindow`) | Read ~109 lines, patch whole body | Fetch ~same symbol body, resend whole body | **Near tie**, slight Serena advantage only from structural targeting |
|
||||
| **Cross-file rename** (`wait -> delay`) | Read 4 files, craft 4-file patch | One rename after discovery | **Serena by a large margin** |
|
||||
|
||||
### Forced reads
|
||||
|
||||
- Built-ins often needed a **localization read** before the edit.
|
||||
- Serena avoided that when the symbol was already known, but not when the task itself required understanding the body.
|
||||
|
||||
### Stable vs ephemeral addressing
|
||||
|
||||
- Serena's addresses (`createMainWindow`, `wait`, `openStreetMapUserAgent`) stayed useful across later operations.
|
||||
- Built-in line ranges from `view` became stale after edits, so later text operations required re-grepping or re-viewing.
|
||||
|
||||
**Verdict:** Serena is most token-efficient for medium-to-large symbol work and cross-file refactors; built-ins stay leaner for tiny local edits.
|
||||
|
||||
## 5. Reliability & correctness (under correct use)
|
||||
|
||||
- **Precision of matching:** Serena's reference search answered "who uses this in code?" better than `rg`, which mixed real uses with prose/comment matches.
|
||||
- **Scope disambiguation:** Serena targeted exact symbols (`AutoLauncher/toggleAutoLaunch`, `InferenceBackend`) rather than relying on unique text strings.
|
||||
- **Atomicity:** Serena computed and updated semantic scope in one refactor call; built-ins could batch edits, but only after manual scope discovery.
|
||||
- **Semantic queries vs text search:** hierarchy and references were the strongest examples. Built-ins could reconstruct them, but only with manual interpretation.
|
||||
- **External dependencies:** after indexing was available, Serena resolved desktop TypeScript dependencies into package-local declaration files under `desktop/node_modules` and Rust dependencies into external Cargo sources such as `url` and `zeroize`. Built-ins could still reach those files, but only after manual package-root or registry-path discovery.
|
||||
- **Monorepo effect:** this repo magnified Serena's dependency-lookup value because "the dependency source" was not at one obvious global root. Serena jumped from app code to the right package-local or registry-backed dependency context directly.
|
||||
|
||||
**Verdict:** Serena improved correctness by narrowing work to exact symbols and by resolving dependencies across monorepo boundaries.
|
||||
|
||||
## 6. Workflow effects across a session
|
||||
|
||||
- **Advantages compounded** when I stayed in symbol space. Example: `get_symbols_overview(main.ts)` produced symbol names that I later reused for `find_symbol(createMainWindow)`, `rename(openStreetMapUserAgent)`, and `inline(waitForRendererDevServer)`.
|
||||
- **Built-in workflows required refreshes**. Across repeated `main.ts` experiments, I repeatedly had to reacquire ranges with `view`/`rg` before editing because prior line-based context was no longer trustworthy.
|
||||
- **In the monorepo, Serena also compounded by removing package-boundary bookkeeping.** In the desktop app I could jump from `main.ts` into Electron and `next-electron-server` declarations without first reasoning about workspace roots; in `rust/core` I could jump into Cargo-registry dependencies through external symbol handles instead of manually reconstructing registry paths from `Cargo.lock`.
|
||||
- **The compounding effect disappeared** for tiny edits and non-code work, where built-ins were already direct and minimal.
|
||||
- **One tradeoff compounded too:** some Serena refactors carried formatting side effects (notably `inline`), so the semantic benefit does not guarantee a surgically small diff.
|
||||
|
||||
**Verdict:** Serena's advantages compound most in code-centric monorepo sessions, where symbol reuse and dependency jumps save both re-reading and package-root discovery work.
|
||||
|
||||
## 7. Unique capabilities
|
||||
|
||||
| Capability with no practical one-step built-in equivalent | Frequency | Impact |
|
||||
| --- | --- | --- |
|
||||
| **Semantic cross-file rename from a single symbol definition** | Medium | High |
|
||||
| **Type hierarchy query (implementations / supertypes)** | Low-Medium | Medium |
|
||||
| **Inline refactor across call sites** | Low | High when applicable |
|
||||
| **File move with import updates** | Low-Medium | High |
|
||||
| **External dependency resolution from in-repo code into package-local or registry-backed sources** | Medium | Medium-High |
|
||||
|
||||
Built-ins can approximate all of these manually, but not as a single semantic operation.
|
||||
|
||||
**Verdict:** Serena did add unique practical capabilities, especially around refactors that require scope computation rather than text replacement.
|
||||
|
||||
## 8. Tasks outside Serena's scope (built-in only)
|
||||
|
||||
- Reading non-code files like `desktop/package.json`
|
||||
- Free-text search such as `ente://app` or URL strings
|
||||
- Git inspection / diff / cleanup
|
||||
- Config/package/changelog/docs/notebook reading
|
||||
- Exact textual patching once the line range is already known
|
||||
|
||||
In this session, these built-in-only tasks were **roughly 40% of the total operational steps by count**, but they were usually the low-complexity steps around the more valuable semantic work.
|
||||
|
||||
**Verdict:** A substantial share of everyday terminal work remains built-in-only, but Serena targets the higher-value symbol-heavy slice rather than the whole session.
|
||||
|
||||
## 9. Practical usage rule
|
||||
|
||||
Use **Serena first** when the task is about a **code symbol** and especially when it spans **multiple files, references, or a whole symbol body**. Use **built-ins first** when the task is about **text, config/docs, free-text search, git state, or a 1-3 line local tweak**. The highest-yield mixed workflow in this repo was: **discover/refactor with Serena, inspect non-code and do tiny patches with built-ins**.
|
||||
|
||||
**Verdict:** Choose Serena for symbol semantics and built-ins for text locality.
|
||||
+1
-1
@@ -102,7 +102,7 @@ sphinx:
|
||||
local_extensions : # A list of local extensions to load by sphinx specified by "name: path" items
|
||||
recursive_update : false # A boolean indicating whether to overwrite the Sphinx config (true) or recursively update (false)
|
||||
config : # key-value pairs to directly over-ride the Sphinx configuration
|
||||
master_doc: "01-about/000_intro.md"
|
||||
master_doc: "01-about/000_evaluation-results.md"
|
||||
html_theme_options:
|
||||
logo:
|
||||
image_light: ../resources/serena-logo.svg
|
||||
|
||||
+58
-3
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Optional, List
|
||||
@@ -152,9 +153,10 @@ def autogen_tool_list(target_filename = "01-about/035_tools.md"):
|
||||
with open(target_file, "w") as f:
|
||||
f.write("<!-- This file is auto-generated by docs/autogen_docs.py. Do not edit it manually. -->\n\n")
|
||||
f.write("# Tools\n\n")
|
||||
f.write("Find the full list of Serena's tools below. \n")
|
||||
f.write("Find the full list of Serena's tools below.\n\n")
|
||||
f.write("Note that in most configurations, only a subset of these tools will be enabled simultaneously.\n")
|
||||
f.write("Tools marked as optional are disabled by default.\n\n")
|
||||
f.write("Tools marked as *optional* are disabled by default.\n\n")
|
||||
f.write("Tools marked as [BETA] were recently introduced and may not be fully robust yet.\n\n")
|
||||
tools_by_module = ToolRegistry().get_registered_tools_by_module()
|
||||
priority_modules = {"serena.tools.symbol_tools": 1, "serena.tools.jetbrains_tools": 2}
|
||||
|
||||
@@ -165,16 +167,69 @@ def autogen_tool_list(target_filename = "01-about/035_tools.md"):
|
||||
module = module.replace("serena.tools.", "")
|
||||
text.with_line(f"* **{module}**")
|
||||
for tool in tools:
|
||||
info = " *(optional)*" if tool.is_optional else ""
|
||||
info = ""
|
||||
if tool.is_optional:
|
||||
info += " *(optional)*"
|
||||
if tool.is_beta:
|
||||
info += " [BETA]"
|
||||
text.with_line(f"* `{tool.tool_name}`{info}: {tool.class_docstring}", indent=2)
|
||||
f.write(text.build())
|
||||
|
||||
|
||||
def autogen_about_intro_features():
|
||||
readme_path = Path(__file__).parent.parent / "README.md"
|
||||
with open(readme_path, "r", encoding="utf-8") as f:
|
||||
readme_contents = f.read()
|
||||
|
||||
match = re.search(r"<h3.*?>(.*?)</h3>(.*?)^## Programming.*?^(## Features.*?)^## ", readme_contents, re.DOTALL | re.MULTILINE)
|
||||
assert match, f"Failed to extract about texts from README.md. {__file__} probably needs to be updated."
|
||||
|
||||
tagline = match.group(1).strip()
|
||||
about_text = match.group(2).strip()
|
||||
features_text = match.group(3).strip()
|
||||
|
||||
autogen_info = f"<!-- This section is auto-generated by {__file__} from the root README.md; do not edit. -->\n\n"
|
||||
|
||||
with open(Path(__file__).parent / "01-about" / "000_intro.md", "w", encoding="utf-8") as f:
|
||||
f.write(autogen_info)
|
||||
f.write("# About Serena\n\n")
|
||||
f.write(f"**{tagline}**\n\n")
|
||||
|
||||
# adjust link
|
||||
about_text = about_text.replace("resources/serena-block-diagram.svg", "https://raw.githubusercontent.com/oraios/serena/main/resources/serena-block-diagram.svg")
|
||||
|
||||
# remove centred links
|
||||
about_text = re.subn(r'^<div align="center">.*?</div>\s*<br>$', "", about_text, flags=re.MULTILINE | re.DOTALL)[0]
|
||||
|
||||
# remove statements with links to quick start guide
|
||||
about_text = re.subn(r"^.*\[Quick Start.*?$", r"", about_text, flags=re.MULTILINE)[0]
|
||||
|
||||
# remove callouts
|
||||
about_text = re.subn(r"^> \[!\w+\]\s+(^>.*?$)+", "", about_text, flags=re.MULTILINE)[0]
|
||||
|
||||
f.write(f"{about_text}\n\n")
|
||||
|
||||
jetbrains_marketplace_link = ('```{raw} html\n'
|
||||
'<p><a href="https://plugins.jetbrains.com/plugin/28946-serena/">'
|
||||
'<img style="background-color:transparent;" src="../_static/images/jetbrains-marketplace-button.png">'
|
||||
'</a></p>\n```')
|
||||
|
||||
with open(Path(__file__).parent / "01-about" / "025_features.md", "w", encoding="utf-8") as f:
|
||||
f.write(autogen_info)
|
||||
features_text = re.subn(r"^#", r"", features_text, flags=re.MULTILINE)[0]
|
||||
features_text = re.subn(r"</?details>", "", features_text, flags=re.MULTILINE)[0]
|
||||
features_text = re.subn(r"<summary>.*?</summary>", "", features_text, flags=re.MULTILINE)[0]
|
||||
features_text = re.sub(r'<a href="https://plugins.jetbrains.com.*?</a>', jetbrains_marketplace_link, features_text, flags=re.DOTALL)
|
||||
f.write(f"{features_text}\n\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
docs_root = Path(__file__).parent
|
||||
enable_module_docs = False
|
||||
|
||||
autogen_about_intro_features()
|
||||
|
||||
autogen_tool_list()
|
||||
|
||||
if enable_module_docs:
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,29 +0,0 @@
|
||||
# MCP Installation instructions
|
||||
|
||||
This document is mainly used as instructions for AI-assistants like Cline and others that
|
||||
try to do an automatic install based on freeform instructions.
|
||||
|
||||
0. Make sure `uv` is installed. If not, install it using either `curl -LsSf https://astral.sh/uv/install.sh | sh` (macOS, Linux) or
|
||||
`powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"` (Windows). Find the path to the `uv` executable,
|
||||
you'll need it later.
|
||||
1. Clone the repo with `git clone git@github.com:oraios/serena.git` and change into its dir (e.g., `cd serena`)
|
||||
2. Check if `serena_config.yml` exists. If not, create it with `cp serena_config.template.yml serena_config.yml`. Read the instructions in the config.
|
||||
3. In the config, check if the path to your project was added. If not, add it to the `projects` section
|
||||
4. In your project, create a `.serena` if needed and check whether `project.yml` exists there.
|
||||
5. If no `project.yml` was found, create it using `cp /path/to/serena/myproject.template.yml /path/to/your/project/.serena/project.yml`
|
||||
6. Read the instructions in `project.yml`. Make sure the `project.yml` has the correct project language configured.
|
||||
Remove the project_root entry there.
|
||||
7. Finally, add the Serena MCP server config like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
...
|
||||
"serena": {
|
||||
"command": "/abs/path/to/uv",
|
||||
"args": ["run", "--directory", "/abs/path/to/serena", "serena-mcp-server", "/path/to/your/project/.serena/project.yml"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
@@ -8,7 +8,7 @@
|
||||
<li><strong>Extended symbol information:</strong> The `find_symbol` and `find_referencing_symbols` tools (and their JetBrains variants)
|
||||
now can return more information about the symbol (specifically, docstrings and signatures).
|
||||
</li>
|
||||
<li><strong>Type hierarchy tool:</strong> A new tool – exclusive to <a href="https://oraios.github.io/serena/02-usage/025_jetbrains_plugin.html">JetBrains mode</a> –
|
||||
<li><strong>Type hierarchy tool:</strong> A new tool – exclusive to <a target="_blank" href="https://oraios.github.io/serena/02-usage/025_jetbrains_plugin.html">JetBrains mode</a> –
|
||||
that can fetch a symbol's hierarchy, i.e. superclasses and subclasses, which is very useful in many situations.
|
||||
</li>
|
||||
<li><strong>Compact overviews:</strong> The `get_symbols_overview` tools now return a much more compact
|
||||
@@ -17,6 +17,6 @@
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
For more detailed information, see our <a href="https://github.com/oraios/serena/blob/main/CHANGELOG.md">changelog</a>.
|
||||
For more detailed information, see our <a target="_blank" href="https://github.com/oraios/serena/blob/main/CHANGELOG.md">changelog</a>.
|
||||
</p>
|
||||
</div>
|
||||
@@ -7,6 +7,6 @@
|
||||
into topics and sharing them across projects.
|
||||
</p>
|
||||
<p>
|
||||
For more detailed information, see the <a href="https://oraios.github.io/serena/02-usage/045_memories.html">documentation</a>.
|
||||
For more detailed information, see the <a target="_blank" href="https://oraios.github.io/serena/02-usage/045_memories.html">documentation</a>.
|
||||
</p>
|
||||
</div>
|
||||
File renamed without changes.
@@ -8,6 +8,6 @@
|
||||
to other projects.
|
||||
</p>
|
||||
<p>
|
||||
Find out more about this feature in our <a href="https://oraios.github.io/serena/02-usage/040_workflow.html#reading-from-external-projects">documentation</a>.
|
||||
Find out more about this feature in our <a target="_blank" href="https://oraios.github.io/serena/02-usage/040_workflow.html#reading-from-external-projects">documentation</a>.
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
<div class="news-item">
|
||||
<h3>Serena v1.0 Is Here!</h3>
|
||||
<p class="date">April 3, 2026</p>
|
||||
<p>
|
||||
After a full year of work since the first version of Serena, we are excited to announce the release of Serena v1.0!
|
||||
</p>
|
||||
<p>
|
||||
Since our last update, we have now added support for <b>exciting new retrieval and refactoring tools</b>, especially for users of the JetBrains backend.
|
||||
</p>
|
||||
<ul>
|
||||
<li>safe_delete, jet_brains_safe_delete (beta):<br> Safely deletes a symbol, checking for remaining usages first</li>
|
||||
<li>jet_brains_move (beta):<br> Moves a symbol, file or directory to a new location, updating all references</li>
|
||||
<li>jet_brains_find_declaration:<br> Finds the declaration of a symbol using the JetBrains backend</li>
|
||||
<li>jet_brains_find_implementations:<br> Finds the implementations of a symbol (e.g. abstract method)</li>
|
||||
<li>jet_brains_inline_symbol (beta):<br> Inlines a symbol, replacing all call sites with the symbol’s body</li>
|
||||
</ul>
|
||||
<p>
|
||||
We are far from done. Many new features will be coming your way – for both the LSP and JetBrains backends.
|
||||
</p>
|
||||
<p>
|
||||
Thank you to our contributors, users and customers!
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
<div class="news-item">
|
||||
<h3>Serena v1.1: Hooks and New Release Mechanism</h3>
|
||||
<p class="date">April 11, 2026</p>
|
||||
<p>
|
||||
<b>Installation.</b> Serena has matured, and we are switching to a PyPI-based release and installation mechanism.
|
||||
The recommended uv tool-based installation decreases startup times and facilitates CLI usage.
|
||||
See our <a target="_blank" href="https://oraios.github.io/serena/02-usage/010_installation.html">updated installation instructions</a>
|
||||
for more details,
|
||||
and update your <a target="_blank" href="https://oraios.github.io/serena/02-usage/030_clients.html">client configurations</a> after having switched
|
||||
to the new installation method.
|
||||
</p>
|
||||
<p>
|
||||
<b>Improving Tool Usage via Hooks.</b> To address issues with agents not using Serena's tools (agent drift), we now provide the `serena-hooks` commands and a recommended
|
||||
setup for Claude Code and VSCode.
|
||||
Please read the <a target="_blank" href="https://oraios.github.io/serena/02-usage/030_clients.html">updated documentation</a> for more details.
|
||||
We <i>strongly recommend using the hooks</i>, since recent updates in Claude Code and VSCode resulted in
|
||||
reduced performance, which our hook commands compensate for.
|
||||
</p>
|
||||
<p>
|
||||
<b>Community.</b> We opened a <a target="_blank" href="https://discord.com/invite/cVUNQmnV4r">Discord server</a> for our community.
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="news-item">
|
||||
<h3>Agents Evaluating Serena for Themselves & Maintenance Update</h3>
|
||||
<p class="date">April 14, 2026</p>
|
||||
<p>
|
||||
<b>Evaluation.</b>
|
||||
We had our actual "end users", i.e. coding agents, evaluate the added value of Serena's tools based on their own empirical insights.
|
||||
Read what the agents had to say in our <a target="_blank" href="https://oraios.github.io/serena/04-evaluation/000_evaluation-intro.html">evaluation report</a>.<br>
|
||||
This represents the first systematic evaluation we ever conducted.
|
||||
</p>
|
||||
<p>
|
||||
<b>Maintenance Update.</b> The new v1.1.2 release adds several improvements and fixes.<br>
|
||||
If you have already switched to the uv tool installation of Serena, upgrade as follows:<br>
|
||||
uv tool upgrade serena-agent --prerelease=allow
|
||||
</p>
|
||||
</div>
|
||||
+15
-9
@@ -4,14 +4,17 @@ requires = ["hatchling"]
|
||||
|
||||
[project]
|
||||
name = "serena-agent"
|
||||
version = "0.1.4"
|
||||
description = ""
|
||||
version = "1.1.2"
|
||||
description = "A powerful MCP toolkit for coding, providing semantic retrieval and editing capabilities - the IDE for your agent"
|
||||
authors = [{ name = "Oraios AI", email = "info@oraios-ai.de" }]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11, <3.15"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
]
|
||||
dependencies = [
|
||||
"requests==2.33.0",
|
||||
@@ -36,22 +39,22 @@ dependencies = [
|
||||
"tiktoken==0.12.0",
|
||||
"anthropic==0.59.0",
|
||||
"beautifulsoup4==4.14.2",
|
||||
"pywebview",
|
||||
"pywebview==6.2",
|
||||
"pystray==0.19.5",
|
||||
"pygls==2.1.1", # used to implement an msl language server
|
||||
"lsprotocol==2025.0.0",
|
||||
# Transitive deps pinned for security (dependabot alerts).
|
||||
# Exact pins because uvx installs from git, ignoring the lock file.
|
||||
"urllib3==2.6.3",
|
||||
"werkzeug==3.1.7",
|
||||
"starlette==1.0.0",
|
||||
"python-multipart==0.0.22",
|
||||
"python-multipart==0.0.26",
|
||||
"filelock==3.25.2",
|
||||
"cryptography==46.0.6",
|
||||
"cryptography==46.0.7",
|
||||
"regex==2026.2.28",
|
||||
"pythonnet==3.1.0-rc0 ; sys_platform == 'win32'",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
pywebview = { git = "https://github.com/r0x0r/pywebview", rev = "345596c1f4fd59fd26c7aa02d4176c5362bcbabe" }
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "testpypi"
|
||||
@@ -61,8 +64,7 @@ explicit = true
|
||||
|
||||
[project.scripts]
|
||||
serena = "serena.cli:top_level"
|
||||
serena-mcp-server = "serena.cli:start_mcp_server"
|
||||
index-project = "serena.cli:index_project" # deprecated
|
||||
serena-hooks = "serena.hooks:hook_commands"
|
||||
|
||||
[project.license]
|
||||
text = "MIT"
|
||||
@@ -83,6 +85,7 @@ dev = [
|
||||
# docs
|
||||
"sphinx==7.4.7",
|
||||
"sphinx_rtd_theme==2.0.0",
|
||||
"sphinx_book_theme==1.1.4",
|
||||
"sphinx-toolbox==3.7.0",
|
||||
"jupyter-book==1.0.4.post1",
|
||||
"nbsphinx==0.9.7",
|
||||
@@ -298,6 +301,7 @@ max-complexity = 20
|
||||
addopts = "--snapshot-patch-pycharm-diff"
|
||||
markers = [
|
||||
"clojure: language server running for Clojure",
|
||||
"crystal: language server running for Crystal",
|
||||
"python: language server running for Python",
|
||||
"go: language server running for Go",
|
||||
"java: language server running for Java",
|
||||
@@ -332,6 +336,7 @@ markers = [
|
||||
"julia: Julia language server tests",
|
||||
"fortran: language server running for Fortran",
|
||||
"haskell: Haskell language server tests",
|
||||
"haxe: Haxe language server tests",
|
||||
"yaml: language server running for YAML",
|
||||
"powershell: language server running for PowerShell",
|
||||
"pascal: language server running for Pascal (Free Pascal/Lazarus)",
|
||||
@@ -344,6 +349,7 @@ markers = [
|
||||
"lean4: language server running for Lean 4",
|
||||
"solidity: language server running for Solidity (uses @nomicfoundation/solidity-language-server)",
|
||||
"ansible: language server running for Ansible (uses @ansible/ansible-language-server)",
|
||||
"msl: language server running for mSL (mIRC Scripting Language)",
|
||||
]
|
||||
|
||||
[tool.codespell]
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-miterlimit:22.9256" viewBox="0 0 2622 1276"><path d="M0 .84h2622v1275.57H0z" style="fill:none" transform="translate(0 .22)"/><path d="M1308.77 122.74h1199.99v1030.49H1308.77z" style="fill:#fff" transform="matrix(2.18235 0 0 1.25067 -2855.53 -154.85)"/><path d="M.479-.239a.4.4 0 0 1-.012.104.3.3 0 0 1-.037.078.17.17 0 0 1-.141.066.15.15 0 0 1-.101-.037l-.033-.03v.22q0 .004-.002.007L.147.174.135.177l-.02.001-.02-.001L.083.174.076.169.075.162v-.614l.001-.007q.001-.003.006-.004l.011-.003.017-.001.017.001.011.003q.005.001.006.004l.002.007v.059l.038-.035A.2.2 0 0 1 .26-.469a.2.2 0 0 1 .123.014.14.14 0 0 1 .055.051.2.2 0 0 1 .031.075.4.4 0 0 1 .01.09m-.085.01a1 1 0 0 0-.005-.066.2.2 0 0 0-.018-.056.1.1 0 0 0-.033-.039.1.1 0 0 0-.052-.014.1.1 0 0 0-.062.019.3.3 0 0 0-.069.067v.174a.3.3 0 0 0 .063.062q.03.022.063.022a.1.1 0 0 0 .052-.015.1.1 0 0 0 .035-.039.2.2 0 0 0 .02-.054.3.3 0 0 0 .006-.061" style="fill-rule:nonzero" transform="translate(487.866 674.342)scale(46.26906)"/><path d="m.332-.423-.001.018-.002.012q-.001.004-.004.006-.002.003-.006.002-.003 0-.009-.002L.297-.391.281-.396.262-.397a.1.1 0 0 0-.024.004.1.1 0 0 0-.024.017.2.2 0 0 0-.027.029l-.032.046v.289q0 .004-.002.006l-.006.005-.012.003-.02.001-.02-.001-.012-.003-.007-.005-.001-.006v-.44l.001-.006.006-.005.011-.003.018-.001.018.001q.008 0 .011.003l.006.005.001.006v.064q.02-.026.034-.043l.03-.026A.1.1 0 0 1 .24-.47q.013-.004.028-.004l.014.001.017.003.016.004.01.005.004.005.001.005.001.01z" style="fill-rule:nonzero" transform="translate(512.176 674.342)scale(46.26906)"/><path d="M.483-.237a.3.3 0 0 1-.014.099.2.2 0 0 1-.042.078.2.2 0 0 1-.07.051.3.3 0 0 1-.098.018.3.3 0 0 1-.095-.016.2.2 0 0 1-.067-.047.2.2 0 0 1-.04-.074.4.4 0 0 1-.013-.1q0-.054.014-.099A.2.2 0 0 1 .1-.405a.2.2 0 0 1 .07-.051.3.3 0 0 1 .098-.018q.054 0 .095.016a.2.2 0 0 1 .067.047.2.2 0 0 1 .04.075.3.3 0 0 1 .013.099m-.085.006a.4.4 0 0 0-.006-.068.2.2 0 0 0-.022-.055.1.1 0 0 0-.041-.038.13.13 0 0 0-.064-.014.2.2 0 0 0-.062.012.1.1 0 0 0-.042.036.2.2 0 0 0-.025.055.3.3 0 0 0-.001.137q.007.033.022.056.015.024.041.037.026.014.065.014a.2.2 0 0 0 .061-.012.1.1 0 0 0 .042-.036.2.2 0 0 0 .025-.055.3.3 0 0 0 .007-.069" style="fill-rule:nonzero" transform="translate(527.561 674.342)scale(46.26906)"/><path d="m.727-.012-.002.006-.007.005-.012.003-.019.001-.02-.001-.013-.003-.007-.005-.001-.006v-.267A.3.3 0 0 0 .641-.33a.1.1 0 0 0-.016-.04.1.1 0 0 0-.027-.025.1.1 0 0 0-.039-.009.1.1 0 0 0-.056.022.3.3 0 0 0-.062.063v.307l-.002.006q-.002.003-.007.005L.42.002.4.003.381.002.368-.001.362-.006.36-.012v-.267A.2.2 0 0 0 .355-.33a.1.1 0 0 0-.016-.04.1.1 0 0 0-.027-.025.1.1 0 0 0-.039-.009.1.1 0 0 0-.056.022.3.3 0 0 0-.062.063v.307q0 .004-.002.006l-.006.005-.012.003-.02.001-.02-.001-.012-.003-.007-.005-.001-.006v-.44l.001-.006.006-.005.011-.003.018-.001.018.001q.008 0 .011.003l.006.005.001.006v.058a.3.3 0 0 1 .072-.06.15.15 0 0 1 .12-.013q.022.006.038.018a.1.1 0 0 1 .029.027.1.1 0 0 1 .019.035.5.5 0 0 1 .08-.067.14.14 0 0 1 .071-.02q.042 0 .071.015a.13.13 0 0 1 .047.04q.017.025.025.058a.3.3 0 0 1 .008.071z" style="fill-rule:nonzero" transform="translate(551.96 674.342)scale(46.26906)"/><path d="M.479-.239a.4.4 0 0 1-.012.104.3.3 0 0 1-.037.078.17.17 0 0 1-.141.066.15.15 0 0 1-.101-.037l-.033-.03v.22q0 .004-.002.007L.147.174.135.177l-.02.001-.02-.001L.083.174.076.169.075.162v-.614l.001-.007q.001-.003.006-.004l.011-.003.017-.001.017.001.011.003q.005.001.006.004l.002.007v.059l.038-.035A.2.2 0 0 1 .26-.469a.2.2 0 0 1 .123.014.14.14 0 0 1 .055.051.2.2 0 0 1 .031.075.4.4 0 0 1 .01.09m-.085.01a1 1 0 0 0-.005-.066.2.2 0 0 0-.018-.056.1.1 0 0 0-.033-.039.1.1 0 0 0-.052-.014.1.1 0 0 0-.062.019.3.3 0 0 0-.069.067v.174a.3.3 0 0 0 .063.062q.03.022.063.022a.1.1 0 0 0 .052-.015.1.1 0 0 0 .035-.039.2.2 0 0 0 .02-.054.3.3 0 0 0 .006-.061" style="fill-rule:nonzero" transform="translate(588.921 674.342)scale(46.26906)"/><path d="M.303-.044q0 .013-.002.022T.295-.01q-.003.004-.011.008L.266.003.245.007.222.008a.2.2 0 0 1-.058-.009.1.1 0 0 1-.04-.027.1.1 0 0 1-.023-.046.3.3 0 0 1-.007-.067v-.256H.032q-.008 0-.011-.008a.1.1 0 0 1-.005-.026l.001-.015.004-.011.005-.006.007-.001h.061v-.105l.001-.006.007-.005.012-.003.02-.001.02.001q.007 0 .012.003l.006.005q.003.003.002.006v.105h.113l.007.001.005.006.003.011.001.015a.1.1 0 0 1-.004.026q-.005.008-.012.008H.174v.245q0 .045.014.068t.048.023q.01 0 .02-.002l.015-.004.012-.005.009-.002.005.001.003.005.002.009z" style="fill-rule:nonzero" transform="translate(613.028 674.342)scale(46.26906)"/><path d="M.351-.132a.14.14 0 0 1-.013.06.1.1 0 0 1-.035.045A.2.2 0 0 1 .248 0a.315.315 0 0 1-.114.006.3.3 0 0 1-.067-.021L.05-.026A.03.03 0 0 1 .042-.04Q.039-.049.04-.064L.041-.08l.002-.01.004-.005Q.05-.098.053-.097q.006 0 .016.006a.2.2 0 0 0 .062.029q.02.007.049.007A.1.1 0 0 0 .217-.06a.1.1 0 0 0 .029-.013.05.05 0 0 0 .018-.021.1.1 0 0 0 .007-.031.05.Line truncated
|
||||
|
After Width: | Height: | Size: 125 KiB |
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
Script to build a single JSON file from all individual news HTML files.
|
||||
|
||||
Usage:
|
||||
uv run python scripts/build_news_json.py
|
||||
|
||||
This reads all .html files from the `news/` directory and creates
|
||||
`news/news.json` containing a mapping of news IDs to HTML content strings.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from serena.config.serena_config import SerenaPaths
|
||||
|
||||
|
||||
def build_news_json() -> None:
|
||||
news_dir = Path(SerenaPaths().news_dir)
|
||||
|
||||
if not news_dir.exists():
|
||||
print(f"Error: News directory not found at {news_dir}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
news_files = sorted(news_dir.glob("*.html"))
|
||||
if not news_files:
|
||||
print("Warning: No HTML news files found in news/", file=sys.stderr)
|
||||
|
||||
news_data: dict[str, str] = {}
|
||||
for news_file in news_files:
|
||||
news_id = news_file.stem # e.g. "20260111"
|
||||
html_content = news_file.read_text(encoding="utf-8").strip()
|
||||
news_data[news_id] = html_content
|
||||
print(f" Added news {news_id} ({len(html_content)} chars)")
|
||||
|
||||
output_file = news_dir / "news.json"
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
json.dump(news_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"\nBuilt {output_file} with {len(news_data)} news entries.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
deploy = False
|
||||
for arg in sys.argv[1:]:
|
||||
if arg == "--deploy":
|
||||
deploy = True
|
||||
|
||||
build_news_json()
|
||||
|
||||
if deploy:
|
||||
user = os.getenv("HADES_USER")
|
||||
assert user, "HADES_USER environment variable must be set to deploy news.json to Hades"
|
||||
os.system(f"scp news/news.json {user}@hades:/var/www/html/oraios-software/serena_news.json")
|
||||
@@ -0,0 +1,229 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
import click
|
||||
|
||||
from serena.constants import REPO_ROOT
|
||||
from serena.util.git import get_git_status
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
VersionPart = Literal["major", "minor", "patch"]
|
||||
_VERSION_PATTERN = re.compile(r"^(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)$")
|
||||
_INIT_VERSION_PATTERN = re.compile(r'^(?P<before>__version__\s*=\s*")(?P<version>\d+\.\d+\.\d+)(?P<after>"\s*)$', re.MULTILINE)
|
||||
_PYPROJECT_VERSION_PATTERN = re.compile(
|
||||
r'(?m)^(?P<before>\[project\]\n(?:.*\n)*?^version\s*=\s*")(?P<version>\d+\.\d+\.\d+)(?P<after>"\s*)$'
|
||||
)
|
||||
_UNRELEASED_HEADER = "# Unreleased (main)\n"
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("--major", "major", is_flag=True, help="Bump the major version and reset minor and patch to 0.")
|
||||
@click.option("--minor", "minor", is_flag=True, help="Bump the minor version and reset patch to 0.")
|
||||
@click.option("--patch", "patch", is_flag=True, help="Bump the patch version.")
|
||||
@click.option("--version", "-v", "target_version", metavar="X.Y.Z", help="Set an explicit version instead of bumping.")
|
||||
@click.option("--dry-run", is_flag=True, help="Show what would change without writing any files.")
|
||||
def bump_version(major: bool, minor: bool, patch: bool, target_version: str | None, dry_run: bool) -> None:
|
||||
git_status = get_git_status()
|
||||
if not git_status.is_clean:
|
||||
raise click.ClickException("Working directory is not clean. Please commit or stash your changes first.")
|
||||
|
||||
log.info("bump_version called: major=%s, minor=%s, patch=%s, target_version=%s", major, minor, patch, target_version)
|
||||
version_part = resolve_version_selection(major=major, minor=minor, patch=patch, target_version=target_version)
|
||||
log.info("Resolved version_part=%s", version_part)
|
||||
|
||||
repo_root = find_repo_root()
|
||||
log.info("Repo root: %s", repo_root)
|
||||
new_version = bump_repo_version(repo_root, version_part=version_part, target_version=target_version, dry_run=dry_run)
|
||||
log.info("New version: %s", new_version)
|
||||
if dry_run:
|
||||
click.echo(f"Dry run complete. Version would be bumped to {new_version}")
|
||||
else:
|
||||
os.system("uv lock")
|
||||
click.echo(f"Bumped version to {new_version}")
|
||||
os.system("git add -u")
|
||||
os.system(f'git commit -m "Release v{new_version}"')
|
||||
os.system(f"git tag v{new_version}")
|
||||
|
||||
|
||||
def find_repo_root() -> Path:
|
||||
return Path(REPO_ROOT)
|
||||
|
||||
|
||||
def resolve_version_selection(*, major: bool, minor: bool, patch: bool, target_version: str | None) -> VersionPart | None:
|
||||
bump_flags_selected = sum([major, minor, patch])
|
||||
if target_version is not None and bump_flags_selected > 0:
|
||||
raise click.ClickException("Use either --version or one of --major/--minor/--patch, not both.")
|
||||
if bump_flags_selected > 1:
|
||||
raise click.ClickException("Use only one of --major, --minor, or --patch.")
|
||||
if target_version is not None:
|
||||
validate_version_string(target_version)
|
||||
return None
|
||||
if major:
|
||||
return "major"
|
||||
if minor:
|
||||
return "minor"
|
||||
if patch:
|
||||
return "patch"
|
||||
raise click.ClickException("No version bump selected. Use --major, --minor, --patch or --version.")
|
||||
|
||||
|
||||
def bump_repo_version(repo_root: Path, *, version_part: VersionPart | None, target_version: str | None, dry_run: bool = False) -> str:
|
||||
pyproject_path = repo_root / "pyproject.toml"
|
||||
init_path = repo_root / "src" / "serena" / "__init__.py"
|
||||
changelog_path = repo_root / "CHANGELOG.md"
|
||||
|
||||
log.info("Reading pyproject.toml from %s", pyproject_path)
|
||||
pyproject_text = pyproject_path.read_text(encoding="utf-8")
|
||||
log.info("Reading __init__.py from %s", init_path)
|
||||
init_text = init_path.read_text(encoding="utf-8")
|
||||
log.info("Reading CHANGELOG.md from %s", changelog_path)
|
||||
changelog_text = changelog_path.read_text(encoding="utf-8")
|
||||
|
||||
log.info("Extracting versions")
|
||||
current_version = extract_version(pyproject_text, _PYPROJECT_VERSION_PATTERN, "pyproject.toml")
|
||||
init_version = extract_version(init_text, _INIT_VERSION_PATTERN, "src/serena/__init__.py")
|
||||
log.info("pyproject.toml version: %s, __init__.py version: %s", current_version, init_version)
|
||||
if current_version != init_version:
|
||||
raise click.ClickException(
|
||||
f"Version mismatch between pyproject.toml and src/serena/__init__.py: {current_version} != {init_version}"
|
||||
)
|
||||
|
||||
if target_version is not None:
|
||||
new_version = validate_version_string(target_version)
|
||||
else:
|
||||
if version_part is None:
|
||||
raise click.ClickException("No version target specified.")
|
||||
new_version = increment_version(current_version, version_part)
|
||||
log.info("New version will be: %s", new_version)
|
||||
|
||||
new_pyproject_text = replace_version(pyproject_text, _PYPROJECT_VERSION_PATTERN, new_version, "pyproject.toml")
|
||||
new_init_text = replace_version(init_text, _INIT_VERSION_PATTERN, new_version, "src/serena/__init__.py")
|
||||
new_changelog_text = update_changelog(changelog_text, new_version)
|
||||
|
||||
file_changes: list[tuple[Path, str, str]] = [
|
||||
(pyproject_path, pyproject_text, new_pyproject_text),
|
||||
(init_path, init_text, new_init_text),
|
||||
(changelog_path, changelog_text, new_changelog_text),
|
||||
]
|
||||
|
||||
if dry_run:
|
||||
for path, old, new in file_changes:
|
||||
if old != new:
|
||||
rel = path.relative_to(repo_root)
|
||||
click.echo(f"\n--- {rel}")
|
||||
_print_diff(old, new)
|
||||
else:
|
||||
for path, _old, new in file_changes:
|
||||
log.info("Writing %s", path)
|
||||
path.write_text(new, encoding="utf-8")
|
||||
log.info("All files written successfully")
|
||||
|
||||
return new_version
|
||||
|
||||
|
||||
def _print_diff(old: str, new: str) -> None:
|
||||
import difflib
|
||||
|
||||
diff = difflib.unified_diff(old.splitlines(), new.splitlines(), lineterm="")
|
||||
# Skip the --- / +++ header lines from unified_diff
|
||||
lines = list(diff)
|
||||
for line in lines[2:]:
|
||||
click.echo(line)
|
||||
|
||||
|
||||
def extract_version(text: str, pattern: re.Pattern[str], file_label: str) -> str:
|
||||
match = pattern.search(text)
|
||||
if match is None:
|
||||
raise click.ClickException(f"Could not find version in {file_label}.")
|
||||
return match.group("version")
|
||||
|
||||
|
||||
def replace_version(text: str, pattern: re.Pattern[str], new_version: str, file_label: str) -> str:
|
||||
match = pattern.search(text)
|
||||
if match is None:
|
||||
raise click.ClickException(f"Could not update version in {file_label}.")
|
||||
return f"{text[: match.start('version')]}{new_version}{text[match.end('version') :]}"
|
||||
|
||||
|
||||
def increment_version(version: str, version_part: VersionPart) -> str:
|
||||
match = _VERSION_PATTERN.fullmatch(version)
|
||||
if match is None:
|
||||
raise click.ClickException(f"Unsupported version format: {version}")
|
||||
|
||||
major = int(match.group("major"))
|
||||
minor = int(match.group("minor"))
|
||||
patch = int(match.group("patch"))
|
||||
|
||||
if version_part == "major":
|
||||
return f"{major + 1}.0.0"
|
||||
if version_part == "minor":
|
||||
return f"{major}.{minor + 1}.0"
|
||||
return f"{major}.{minor}.{patch + 1}"
|
||||
|
||||
|
||||
def validate_version_string(version: str) -> str:
|
||||
if _VERSION_PATTERN.fullmatch(version) is None:
|
||||
raise click.ClickException(f"Unsupported version format: {version}")
|
||||
return version
|
||||
|
||||
|
||||
def update_changelog(changelog_text: str, new_version: str) -> str:
|
||||
if not changelog_text.startswith(_UNRELEASED_HEADER):
|
||||
raise click.ClickException("CHANGELOG.md must start with '# Unreleased (main)'.")
|
||||
|
||||
next_header_index = changelog_text.find("\n# ", len(_UNRELEASED_HEADER))
|
||||
unreleased_section_end = len(changelog_text) if next_header_index == -1 else next_header_index + 1
|
||||
|
||||
unreleased_section = changelog_text[:unreleased_section_end]
|
||||
remaining_text = changelog_text[unreleased_section_end:]
|
||||
unreleased_body = unreleased_section[len(_UNRELEASED_HEADER) :]
|
||||
intro, unreleased_entries = split_unreleased_body(unreleased_body)
|
||||
|
||||
date_str = datetime.now().strftime("%Y-%m-%d")
|
||||
updated_section = _UNRELEASED_HEADER + intro + f"# v{new_version} ({date_str})\n"
|
||||
|
||||
if unreleased_entries.strip():
|
||||
updated_section += "\n" + unreleased_entries.lstrip("\n")
|
||||
else:
|
||||
updated_section += "\n"
|
||||
|
||||
if remaining_text:
|
||||
updated_section += remaining_text.lstrip("\n")
|
||||
|
||||
return updated_section
|
||||
|
||||
|
||||
def split_unreleased_body(unreleased_body: str) -> tuple[str, str]:
|
||||
"""Go until first line with content, the intro will be until the end of that line.
|
||||
|
||||
:return: changelog_intro, changelog_body
|
||||
"""
|
||||
lines = unreleased_body.splitlines(keepends=True)
|
||||
intro_end = 0
|
||||
seen_content = False
|
||||
|
||||
for index, line in enumerate(lines):
|
||||
intro_end = index + 1
|
||||
if line.strip():
|
||||
seen_content = True
|
||||
continue
|
||||
if seen_content:
|
||||
break
|
||||
|
||||
if not seen_content:
|
||||
raise click.ClickException("Could not determine the introduction paragraph in CHANGELOG.md.")
|
||||
|
||||
return "".join(lines[:intro_end]), "".join(lines[intro_end:])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG, format="%(levelname)s %(name)s: %(message)s")
|
||||
log.info("Script starting")
|
||||
bump_version()
|
||||
@@ -178,7 +178,7 @@ if __name__ == "__main__":
|
||||
run_lsp_tools(lsp_agent)
|
||||
run_backend_independent_tools(lsp_agent)
|
||||
finally:
|
||||
lsp_agent.shutdown()
|
||||
lsp_agent.on_shutdown()
|
||||
|
||||
# JetBrains backend (requires a running IDE)
|
||||
try:
|
||||
@@ -186,6 +186,6 @@ if __name__ == "__main__":
|
||||
try:
|
||||
run_jb_tools(jb_agent)
|
||||
finally:
|
||||
jb_agent.shutdown()
|
||||
jb_agent.on_shutdown()
|
||||
except Exception as e:
|
||||
print(f"\nJetBrains backend not available, skipping: {e}")
|
||||
@@ -4,8 +4,11 @@ for testing or development. Here the tools will be operation the serena repo its
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from pprint import pprint
|
||||
|
||||
from toon_format import encode
|
||||
|
||||
from serena.agent import SerenaAgent
|
||||
from serena.config.serena_config import SerenaConfig
|
||||
from serena.constants import REPO_ROOT
|
||||
@@ -14,13 +17,17 @@ from serena.tools import (
|
||||
FindReferencingSymbolsTool,
|
||||
JetBrainsFindSymbolTool,
|
||||
JetBrainsGetSymbolsOverviewTool,
|
||||
JetBrainsInlineSymbol,
|
||||
JetBrainsSafeDeleteTool,
|
||||
SearchForPatternTool,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
serena_config = SerenaConfig.from_config_file()
|
||||
serena_config.web_dashboard = False
|
||||
agent = SerenaAgent(project=REPO_ROOT, serena_config=serena_config)
|
||||
# project = Path(REPO_ROOT).parent / "serena-jetbrains-plugin-copy"
|
||||
project = Path(REPO_ROOT)
|
||||
agent = SerenaAgent(project=str(project), serena_config=serena_config)
|
||||
|
||||
# apply a tool
|
||||
find_symbol_tool = agent.get_tool(JetBrainsFindSymbolTool)
|
||||
@@ -28,9 +35,16 @@ if __name__ == "__main__":
|
||||
find_file_tool = agent.get_tool(FindFileTool)
|
||||
search_pattern_tool = agent.get_tool(SearchForPatternTool)
|
||||
overview_tool = agent.get_tool(JetBrainsGetSymbolsOverviewTool)
|
||||
safe_delete_tool = agent.get_tool(JetBrainsSafeDeleteTool)
|
||||
inline_symbol = agent.get_tool(JetBrainsInlineSymbol)
|
||||
|
||||
result = agent.execute_task(
|
||||
lambda: find_symbol_tool.apply("SerenaAgent/get_tool_description_override"),
|
||||
lambda: overview_tool.apply(
|
||||
# name_path_pattern="SerenaAgent",
|
||||
relative_path="src/serena/agent.py",
|
||||
depth=2,
|
||||
# keep_definition=True,
|
||||
)
|
||||
)
|
||||
pprint(json.loads(result))
|
||||
pprint(encode(json.loads(result)))
|
||||
# input("Press Enter to continue...")
|
||||
@@ -1,4 +1,4 @@
|
||||
from serena.cli import start_mcp_server
|
||||
from serena.cli import top_level
|
||||
|
||||
if __name__ == "__main__":
|
||||
start_mcp_server()
|
||||
top_level.start_mcp_server()
|
||||
@@ -1,4 +1,4 @@
|
||||
__version__ = "0.1.4"
|
||||
__version__ = "1.1.2"
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
+194
-47
@@ -2,8 +2,11 @@
|
||||
The Serena Model Context Protocol (MCP) Server
|
||||
"""
|
||||
|
||||
import json
|
||||
import multiprocessing
|
||||
import os
|
||||
import platform
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
from collections.abc import Callable, Iterator, Sequence
|
||||
@@ -12,6 +15,8 @@ from datetime import datetime
|
||||
from logging import Logger
|
||||
from typing import TYPE_CHECKING, Optional, TypeVar
|
||||
|
||||
import requests
|
||||
import webview
|
||||
from sensai.util import logging
|
||||
from sensai.util.logging import LogTime
|
||||
from sensai.util.string import dict_string
|
||||
@@ -29,12 +34,21 @@ from serena.config.serena_config import (
|
||||
SerenaPaths,
|
||||
ToolInclusionDefinition,
|
||||
)
|
||||
from serena.dashboard import SerenaDashboardAPI, SerenaDashboardTrayManager
|
||||
from serena.dashboard import SerenaDashboardAPI, SerenaDashboardTrayManager, SerenaDashboardViewer
|
||||
from serena.ls_manager import LanguageServerManager
|
||||
from serena.project import MemoriesManager, Project
|
||||
from serena.prompt_factory import SerenaPromptFactory
|
||||
from serena.task_executor import TaskExecutor
|
||||
from serena.tools import ActivateProjectTool, GetCurrentConfigTool, OpenDashboardTool, ReplaceContentTool, Tool, ToolMarker, ToolRegistry
|
||||
from serena.tools import (
|
||||
ActivateProjectTool,
|
||||
GetCurrentConfigTool,
|
||||
OpenDashboardTool,
|
||||
ReadMemoryTool,
|
||||
ReplaceContentTool,
|
||||
Tool,
|
||||
ToolMarker,
|
||||
ToolRegistry,
|
||||
)
|
||||
from serena.util.gui import system_has_usable_display
|
||||
from serena.util.inspection import iter_subclasses
|
||||
from serena.util.logging import MemoryLogHandler
|
||||
@@ -80,6 +94,9 @@ class AvailableTools:
|
||||
def contains_tool_name(self, tool_name: str) -> bool:
|
||||
return tool_name in self._tool_name_set
|
||||
|
||||
def contains_tool_class(self, tool_class: type[Tool]) -> bool:
|
||||
return self.contains_tool_name(tool_class.get_name_from_cls())
|
||||
|
||||
|
||||
class ToolSet:
|
||||
"""
|
||||
@@ -131,24 +148,20 @@ class ToolSet:
|
||||
tool_names = set()
|
||||
for fixed_tool in definition.fixed_tools:
|
||||
fixed_tool = get_updated_tool_name(fixed_tool)
|
||||
if not registry.is_valid_tool_name(fixed_tool):
|
||||
raise ValueError(f"Invalid tool name '{fixed_tool}' provided for fixed tool set")
|
||||
tool_names.add(fixed_tool)
|
||||
if registry.check_valid_tool_name(fixed_tool, " (in fixed tools set)"):
|
||||
tool_names.add(fixed_tool)
|
||||
log.info(f"{definition} defined a fixed tool set with {len(tool_names)} tools: {', '.join(tool_names)}")
|
||||
else:
|
||||
included_tools = []
|
||||
excluded_tools = []
|
||||
for included_tool in definition.included_optional_tools:
|
||||
included_tool = get_updated_tool_name(included_tool)
|
||||
if not registry.is_valid_tool_name(included_tool):
|
||||
raise ValueError(f"Invalid tool name '{included_tool}' provided for inclusion")
|
||||
if included_tool not in tool_names:
|
||||
if registry.check_valid_tool_name(included_tool, " (in included optional tools)") and included_tool not in tool_names:
|
||||
tool_names.add(included_tool)
|
||||
included_tools.append(included_tool)
|
||||
for excluded_tool in definition.excluded_tools:
|
||||
excluded_tool = get_updated_tool_name(excluded_tool)
|
||||
if not registry.is_valid_tool_name(excluded_tool):
|
||||
raise ValueError(f"Invalid tool name '{excluded_tool}' provided for exclusion")
|
||||
registry.check_valid_tool_name(excluded_tool, " (in excluded tools)")
|
||||
if excluded_tool in tool_names:
|
||||
tool_names.remove(excluded_tool)
|
||||
excluded_tools.append(excluded_tool)
|
||||
@@ -225,6 +238,13 @@ class ActiveModes:
|
||||
self._active_modes.append(mode)
|
||||
return self._active_modes
|
||||
|
||||
# TODO: apply caching like in get_modes
|
||||
def get_default_modes(self) -> Sequence[SerenaAgentMode]:
|
||||
return [SerenaAgentMode.load(mode_name) for mode_name in self._default_modes or []]
|
||||
|
||||
def get_base_modes(self) -> Sequence[SerenaAgentMode]:
|
||||
return [SerenaAgentMode.load(mode_name) for mode_name in self._base_modes or []]
|
||||
|
||||
|
||||
class SerenaAgent:
|
||||
def __init__(
|
||||
@@ -248,15 +268,18 @@ class SerenaAgent:
|
||||
:param memory_log_handler: a MemoryLogHandler instance from which to read log messages; if None, a new one will be created
|
||||
if necessary.
|
||||
"""
|
||||
self._active_project: Project | None = None
|
||||
self._gui_log_viewer: Optional["GuiLogViewer"] = None
|
||||
self._dashboard_viewer_process: multiprocessing.Process | None = None
|
||||
|
||||
self.version = serena_version()
|
||||
|
||||
# obtain serena configuration using the decoupled factory function
|
||||
self.serena_config = serena_config or SerenaConfig.from_config_file()
|
||||
|
||||
# propagate configuration to other components
|
||||
self.serena_config.propagate_settings()
|
||||
|
||||
# project-specific instances, which will be initialized upon project activation
|
||||
self._active_project: Project | None = None
|
||||
|
||||
# determine registered project to be activated (if any)
|
||||
registered_project_to_activate: RegisteredProject | None = (
|
||||
self.serena_config.get_registered_project(project, autoregister=True) if project is not None else None
|
||||
@@ -279,7 +302,6 @@ class SerenaAgent:
|
||||
return memory_log_handler
|
||||
|
||||
# open GUI log window if enabled
|
||||
self._gui_log_viewer: Optional["GuiLogViewer"] = None
|
||||
if self.serena_config.gui_log_window:
|
||||
log.info("Opening GUI window")
|
||||
if platform.system() == "Darwin":
|
||||
@@ -289,7 +311,12 @@ class SerenaAgent:
|
||||
# which uv used as a base, unfortunately)
|
||||
from serena.gui_log_viewer import GuiLogViewer
|
||||
|
||||
self._gui_log_viewer = GuiLogViewer("dashboard", title="Serena Logs", memory_log_handler=get_memory_log_handler())
|
||||
self._gui_log_viewer = GuiLogViewer(
|
||||
"dashboard",
|
||||
title="Serena Logs",
|
||||
memory_log_handler=get_memory_log_handler(),
|
||||
shutdown_handler=lambda: self.shutdown(),
|
||||
)
|
||||
self._gui_log_viewer.start()
|
||||
else:
|
||||
log.debug("GUI window is disabled")
|
||||
@@ -313,7 +340,7 @@ class SerenaAgent:
|
||||
|
||||
# log fundamental information
|
||||
log.info(
|
||||
f"Starting Serena server (version={serena_version()}, process id={os.getpid()}, parent process id={os.getppid()}; "
|
||||
f"Starting Serena server (version={self.version}, process id={os.getpid()}, parent process id={os.getppid()}; "
|
||||
f"language backend={self.serena_config.language_backend.name}); Python version={platform.python_version()}, platform={platform.platform()}"
|
||||
)
|
||||
log.info("Configuration file: %s", self.serena_config.config_file_path)
|
||||
@@ -357,7 +384,7 @@ class SerenaAgent:
|
||||
self.serena_config, self._language_backend, self._context, self._active_modes, self._active_project
|
||||
)
|
||||
self._exposed_tools = self._base_toolset.to_available_tools(self._all_tools)
|
||||
log.info(f"Number of exposed tools: {len(self._exposed_tools)}")
|
||||
log.info(f"Number of exposed tools: {len(self._exposed_tools)}. Exposed tools: {self._exposed_tools.tool_names}")
|
||||
|
||||
# update the active tools (considering the active project, if any)
|
||||
self._active_tools: AvailableTools
|
||||
@@ -386,6 +413,22 @@ class SerenaAgent:
|
||||
if self._gui_log_viewer is not None:
|
||||
self._gui_log_viewer.set_dashboard_url(dashboard_url)
|
||||
|
||||
self._send_usage_info()
|
||||
|
||||
def _send_usage_info(self) -> None:
|
||||
if os.getenv("CI") == "true" or os.getenv("GITHUB_ACTIONS") == "true" or os.getenv("SERENA_USAGE_REPORTING") == "false":
|
||||
return
|
||||
params: dict[str, str | int] = {
|
||||
"os": platform.system(),
|
||||
"dashboard": int(self.serena_config.web_dashboard),
|
||||
"version": self.version,
|
||||
"backend": self._language_backend.value,
|
||||
}
|
||||
try:
|
||||
requests.get("https://oraios-software.de/serena_usage.php", params=params, timeout=1)
|
||||
except Exception as e:
|
||||
log.debug(f"Failed to send usage info: {e}")
|
||||
|
||||
@classmethod
|
||||
def _create_base_toolset(
|
||||
cls,
|
||||
@@ -416,22 +459,35 @@ class SerenaAgent:
|
||||
tool_inclusion_definitions.append(serena_config)
|
||||
tool_inclusion_definitions.append(context)
|
||||
|
||||
# determine whether we are operating in a single-project context
|
||||
# (i.e. the project that is activated at startup is the only project that will be worked with throughout the session)
|
||||
is_single_project = context.single_project and project is not None
|
||||
|
||||
# consider modes
|
||||
# Since modes can be dynamically turned on and off, we don't include their definitions directly,
|
||||
# but for the initially active modes, we make sure that the tools they enable are included.
|
||||
for mode in modes.get_modes():
|
||||
tool_inclusion_definitions.append(
|
||||
NamedToolInclusionDefinition(
|
||||
name=f"InitialModeInclusions[{mode.name}]", included_optional_tools=mode.included_optional_tools
|
||||
# * base modes: These cannot be changed, so they are fully applied
|
||||
for base_mode in modes.get_base_modes():
|
||||
tool_inclusion_definitions.append(base_mode)
|
||||
# * default modes: When not in a single-project context, these modes are dynamic (can later be turned off),
|
||||
# so we consider only their inclusions (but not their exclusions, because these must not be hard)
|
||||
for mode in modes.get_default_modes():
|
||||
if is_single_project:
|
||||
tool_inclusion_definitions.append(mode)
|
||||
else:
|
||||
# Since modes can be dynamically turned on and off, we don't include their definitions directly,
|
||||
# For the initially active dynamic modes, we make sure that the tools they enable are included.
|
||||
tool_inclusion_definitions.append(
|
||||
NamedToolInclusionDefinition(
|
||||
name=f"InitialDynamicModeInclusions[{mode.name}]", included_optional_tools=mode.included_optional_tools
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# When in a single-project context, the agent is assumed to work on a single project, and we thus
|
||||
# want to apply that project's tool exclusions/inclusions from the get-go, limiting the set
|
||||
# of tools that will be exposed to the client.
|
||||
# Furthermore, we disable tools that are only relevant for project activation.
|
||||
# So if the project exists, we apply all the aforementioned exclusions.
|
||||
if context.single_project and project is not None:
|
||||
if is_single_project:
|
||||
assert project is not None
|
||||
log.info(
|
||||
"Applying tool inclusion/exclusion definitions for single-project context based on project '%s'",
|
||||
project.project_name,
|
||||
@@ -524,6 +580,43 @@ class SerenaAgent:
|
||||
"""
|
||||
return self._dashboard_url
|
||||
|
||||
@staticmethod
|
||||
def _start_dashboard_viewer_process_function(url: str, minimized: bool, parent_process_id: int) -> None:
|
||||
"""
|
||||
Main function of the subprocess for starting the dashboard viewer
|
||||
"""
|
||||
try:
|
||||
SerenaDashboardViewer(url, start_minimized=minimized, parent_process_id=parent_process_id).run()
|
||||
except webview.errors.WebViewException as e:
|
||||
log.warning(f"Could not open Serena Dashboard viewer. Cause:\n{e}")
|
||||
# Fall back to opening the browser window if the window was supposed to be shown directly
|
||||
if not minimized:
|
||||
SerenaAgent._open_dashboard_in_browser(url)
|
||||
|
||||
def _start_dashboard_viewer(self, minimized: bool) -> None:
|
||||
"""
|
||||
Starts the dashboard viewer (in a separate process) or, if the current platform does not support it,
|
||||
opens the dashboard in the default web browser.
|
||||
|
||||
:param minimized: whether the dashboard viewer should be started minimized (if supported on the current platform).
|
||||
If the viewer is not supported on the current platform, then this controls whether to open the browser window.
|
||||
"""
|
||||
if not system_has_usable_display():
|
||||
log.info("Not starting the Serena dashboard viewer because no usable display was detected.")
|
||||
return
|
||||
|
||||
url = self.get_dashboard_url()
|
||||
assert url is not None
|
||||
if SerenaDashboardViewer.is_current_platform_supported():
|
||||
self._dashboard_viewer_process = multiprocessing.Process(
|
||||
target=self._start_dashboard_viewer_process_function, args=(url, minimized, os.getpid()), daemon=True
|
||||
)
|
||||
self._dashboard_viewer_process.start()
|
||||
else:
|
||||
log.info("Not starting Serena dashboard viewer because the current platform does not support it; using browser-based fallback")
|
||||
if not minimized:
|
||||
self._open_dashboard_in_browser(url)
|
||||
|
||||
def _ensure_tray_manager_and_register(self, open_on_launch: bool) -> None:
|
||||
"""
|
||||
Ensure the tray manager is running and register this dashboard instance with it.
|
||||
@@ -586,7 +679,7 @@ class SerenaAgent:
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
start_new_session=True, # Detach from parent process
|
||||
start_new_session=False,
|
||||
)
|
||||
|
||||
def get_exposed_tool_instances(self) -> list["Tool"]:
|
||||
@@ -655,11 +748,39 @@ class SerenaAgent:
|
||||
|
||||
# If a project is active at startup, append its activation message
|
||||
if self._active_project is not None:
|
||||
system_prompt += "\n\n" + self._active_project.get_activation_message()
|
||||
system_prompt += "\n\n" + self.get_project_activation_message()
|
||||
|
||||
log.info("System prompt:\n%s", system_prompt)
|
||||
return system_prompt
|
||||
|
||||
def get_project_activation_message(self) -> str:
|
||||
"""
|
||||
:return: a message providing information about the project upon activation (e.g. programming language, memories, initial prompt)
|
||||
:raise: AssertionError if no project is active
|
||||
"""
|
||||
proj = self._active_project
|
||||
assert proj is not None, "A project must be active before calling this."
|
||||
if proj.is_newly_created:
|
||||
msg = f"Created and activated a new project with name '{proj.project_name}' at {proj.project_root}. "
|
||||
else:
|
||||
msg = f"The project with name '{proj.project_name}' at {proj.project_root} is activated."
|
||||
if self._language_backend == LanguageBackend.LSP:
|
||||
languages_str = ", ".join([lang.value for lang in proj.project_config.languages])
|
||||
msg += f"\nProgramming languages: {languages_str}."
|
||||
msg += f"File encoding: {proj.project_config.encoding}."
|
||||
|
||||
include_memories = self._active_tools.contains_tool_class(ReadMemoryTool)
|
||||
if include_memories:
|
||||
project_memories = proj.memories_manager.list_project_memories()
|
||||
if project_memories:
|
||||
msg += (
|
||||
f"\n{json.dumps(project_memories.to_dict())}\n"
|
||||
+ "Use the `read_memory` tool to read these memories later if they are relevant to the task."
|
||||
)
|
||||
if proj.project_config.initial_prompt:
|
||||
msg += f"\nAdditional project-specific instructions:\n {proj.project_config.initial_prompt}"
|
||||
return msg
|
||||
|
||||
def _update_active_modes(self) -> None:
|
||||
"""
|
||||
Updates the active modes based on the Serena configuration, the active project configuration (if any),
|
||||
@@ -733,10 +854,17 @@ class SerenaAgent:
|
||||
"""
|
||||
return self._language_backend == LanguageBackend.LSP
|
||||
|
||||
def _activate_project(self, project: Project, update_active_modes: bool = True, update_active_tools: bool = True) -> None:
|
||||
def _activate_project(self, project: Project, update_active_modes: bool = True, update_active_tools: bool = True) -> bool:
|
||||
"""
|
||||
:return: True if the project was newly activated, False if it was already active
|
||||
"""
|
||||
# check if the project is already active
|
||||
if self._active_project is not None and self._active_project.project_root == project.project_root:
|
||||
return False
|
||||
|
||||
log.info(f"Activating {project.project_name} at {project.project_root}")
|
||||
|
||||
# Check if the project requires a different language backend than the one initialized at startup
|
||||
# check if the project requires a different language backend than the one initialized at startup
|
||||
project_backend = project.project_config.language_backend
|
||||
if project_backend is not None and project_backend != self._language_backend:
|
||||
raise ValueError(
|
||||
@@ -777,14 +905,18 @@ class SerenaAgent:
|
||||
if dashboard_port is not None and SerenaDashboardTrayManager.is_current_platform_supported():
|
||||
SerenaDashboardTrayManager.update_project(dashboard_port, project.project_name)
|
||||
|
||||
return True
|
||||
|
||||
def activate_project_from_path_or_name(
|
||||
self, project_root_or_name: str, update_active_modes: bool = True, update_active_tools: bool = True
|
||||
) -> Project:
|
||||
) -> bool:
|
||||
"""
|
||||
Activate a project from a path or a name.
|
||||
If the project was already registered, it will just be activated.
|
||||
If the argument is a path at which no Serena project previously existed, the project will be created beforehand.
|
||||
Raises ProjectNotFoundError if the project could neither be found nor created.
|
||||
|
||||
:return: True if the project was newly activated, False if it was already active
|
||||
"""
|
||||
project_instance: Project | None = self.serena_config.get_project(project_root_or_name)
|
||||
if project_instance is not None:
|
||||
@@ -799,9 +931,7 @@ class SerenaAgent:
|
||||
f"Existing project names: {self.serena_config.project_names}"
|
||||
)
|
||||
|
||||
self._activate_project(project_instance, update_active_modes=update_active_modes, update_active_tools=update_active_tools)
|
||||
|
||||
return project_instance
|
||||
return self._activate_project(project_instance, update_active_modes=update_active_modes, update_active_tools=update_active_tools)
|
||||
|
||||
def get_active_tool_names(self) -> list[str]:
|
||||
"""
|
||||
@@ -816,12 +946,19 @@ class SerenaAgent:
|
||||
"""
|
||||
return self._active_tools.contains_tool_name(tool_name)
|
||||
|
||||
def tool_is_exposed(self, tool_name: str) -> bool:
|
||||
"""
|
||||
:param tool_name: the name of the tool to check
|
||||
:return: True if the tool is in the exposed tool set, False otherwise
|
||||
"""
|
||||
return self._exposed_tools.contains_tool_name(tool_name)
|
||||
|
||||
def get_current_config_overview(self) -> str:
|
||||
"""
|
||||
:return: a string overview of the current configuration, including the active and available configuration options
|
||||
"""
|
||||
result_str = "Current configuration:\n"
|
||||
result_str += f"Serena version: {serena_version()}\n"
|
||||
result_str += f"Serena version: {self.version}\n"
|
||||
result_str += f"Loglevel: {self.serena_config.log_level}, trace_lsp_communication={self.serena_config.trace_lsp_communication}\n"
|
||||
if self._active_project is not None:
|
||||
result_str += f"Active project: {self._active_project.project_name}\n"
|
||||
@@ -896,28 +1033,38 @@ class SerenaAgent:
|
||||
ToolRegistry().print_tool_overview(self._active_tools.tools)
|
||||
|
||||
def __del__(self) -> None:
|
||||
self.shutdown()
|
||||
self.on_shutdown()
|
||||
|
||||
def shutdown(self, timeout: float = 2.0) -> None:
|
||||
def on_shutdown(self, timeout: float = 2.0) -> None:
|
||||
"""
|
||||
Shuts down the agent, freeing resources and stopping background tasks.
|
||||
Shutdown handler of the agent, freeing resources and stopping background tasks.
|
||||
"""
|
||||
log.info("SerenaAgent is shutting down ...")
|
||||
|
||||
# NOTE: This is called from __del__, so the instance may not be fully initialized (exception during __init__)
|
||||
|
||||
if self._active_project is not None:
|
||||
log.info(f"Shutting down active project '{self._active_project.project_name}' ...")
|
||||
self._active_project.shutdown(timeout=timeout)
|
||||
self._active_project = None
|
||||
if self._gui_log_viewer:
|
||||
log.info("Stopping the GUI log window ...")
|
||||
self._gui_log_viewer.stop()
|
||||
self._gui_log_viewer = None
|
||||
if self._dashboard_viewer_process:
|
||||
log.info("Stopping the dashboard viewer process ...")
|
||||
self._dashboard_viewer_process.terminate()
|
||||
self._dashboard_viewer_process = None
|
||||
if hasattr(self, "_dashboard_port"):
|
||||
if self._dashboard_port is not None and SerenaDashboardTrayManager.is_current_platform_supported():
|
||||
SerenaDashboardTrayManager.unregister_instance(self._dashboard_port)
|
||||
|
||||
if hasattr(self, "_active_project") and self._active_project is not None:
|
||||
self._active_project.shutdown(timeout=timeout)
|
||||
self._active_project = None
|
||||
def shutdown(self) -> None:
|
||||
"""
|
||||
Triggers a hard shutdown of the agent, freeing resources and signalling the process to terminate
|
||||
"""
|
||||
# perform clean-up right away, because kill does not result in normal deletion of the object
|
||||
self.on_shutdown()
|
||||
|
||||
if hasattr(self, "_gui_log_viewer") and self._gui_log_viewer is not None:
|
||||
log.info("Stopping the GUI log window ...")
|
||||
self._gui_log_viewer.stop()
|
||||
self._gui_log_viewer = None
|
||||
# signal process termination
|
||||
os.kill(os.getpid(), signal.SIGTERM)
|
||||
|
||||
def get_tool_by_name(self, tool_name: str) -> Tool:
|
||||
tool_class = ToolRegistry().get_tool_class_by_name(tool_name)
|
||||
|
||||
+100
-41
@@ -17,7 +17,9 @@ from sensai.util.logging import FileLoggerContext, datetime_tag
|
||||
from sensai.util.string import dict_string
|
||||
from tqdm import tqdm
|
||||
|
||||
from serena import serena_version
|
||||
from serena.agent import SerenaAgent
|
||||
from serena.config.client_setup import client_setup_handlers
|
||||
from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode
|
||||
from serena.config.serena_config import (
|
||||
LanguageBackend,
|
||||
@@ -37,6 +39,7 @@ from serena.constants import (
|
||||
from serena.mcp import SerenaMCPFactory
|
||||
from serena.project import Project
|
||||
from serena.tools import FindReferencingSymbolsTool, FindSymbolTool, GetSymbolsOverviewTool, SearchForPatternTool, ToolRegistry
|
||||
from serena.util.cli_util import AutoRegisteringGroup
|
||||
from serena.util.dataclass import get_dataclass_default
|
||||
from serena.util.logging import MemoryLogHandler
|
||||
from solidlsp.ls_config import Language
|
||||
@@ -45,7 +48,7 @@ from solidlsp.util.subprocess_util import subprocess_kwargs
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_MAX_CONTENT_WIDTH = 100
|
||||
_MAX_CONTENT_WIDTH = 200
|
||||
_MODES_EXPLANATION = f"""\b\nBuilt-in mode names or paths to custom mode YAMLs with which to
|
||||
override the default modes defined in the global Serena configuration or
|
||||
the active project.
|
||||
@@ -90,9 +93,6 @@ def find_project_root(root: str | Path | None = None) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
# --------------------- Utilities -------------------------------------
|
||||
|
||||
|
||||
def _open_in_editor(path: str) -> None:
|
||||
"""Open the given file in the system's default editor or viewer."""
|
||||
editor = os.environ.get("EDITOR")
|
||||
@@ -128,32 +128,98 @@ class ProjectType(click.ParamType):
|
||||
PROJECT_TYPE = ProjectType()
|
||||
|
||||
|
||||
class AutoRegisteringGroup(click.Group):
|
||||
"""
|
||||
A click.Group subclass that automatically registers any click.Command
|
||||
attributes defined on the class into the group.
|
||||
|
||||
After initialization, it inspects its own class for attributes that are
|
||||
instances of click.Command (typically created via @click.command) and
|
||||
calls self.add_command(cmd) on each. This lets you define your commands
|
||||
as static methods on the subclass for IDE-friendly organization without
|
||||
manual registration.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, help: str):
|
||||
super().__init__(name=name, help=help)
|
||||
# Scan class attributes for click.Command instances and register them.
|
||||
for attr in dir(self.__class__):
|
||||
cmd = getattr(self.__class__, attr)
|
||||
if isinstance(cmd, click.Command):
|
||||
self.add_command(cmd)
|
||||
|
||||
|
||||
class TopLevelCommands(AutoRegisteringGroup):
|
||||
"""Root CLI group containing the core Serena commands."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(name="serena", help="Serena CLI commands. You can run `<command> --help` for more info on each command.")
|
||||
super().__init__(
|
||||
name="serena",
|
||||
help="Main serena CLI commands. "
|
||||
"Note that you also have access to `serena-hooks` CLI commands which are kept under "
|
||||
"that separate entrypoint for performance reasons, see `serena-hooks --help`. You can run `<command> --help` for more info on each command.",
|
||||
)
|
||||
|
||||
# register --version / -V flag
|
||||
self.params.append(
|
||||
click.Option(
|
||||
["--version", "-V"],
|
||||
is_flag=True,
|
||||
expose_value=False,
|
||||
is_eager=True,
|
||||
callback=self._print_version,
|
||||
help="Show the version and exit.",
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _print_version(ctx: click.Context, _param: click.Parameter, value: bool) -> None:
|
||||
"""Print version string and exit if the flag is set."""
|
||||
if not value:
|
||||
return
|
||||
click.echo(f"Serena {serena_version()}")
|
||||
ctx.exit()
|
||||
|
||||
@staticmethod
|
||||
@click.command(
|
||||
"init",
|
||||
help="Initialize Serena by creating a global config file with the specified default language backend.",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.option(
|
||||
"--language-backend",
|
||||
"-b",
|
||||
type=click.Choice([b.value for b in LanguageBackend]),
|
||||
default=LanguageBackend.LSP.value,
|
||||
show_default=True,
|
||||
help="Default code intelligence backend (can be overridden in the project config).",
|
||||
)
|
||||
def init(language_backend: Literal["LSP", "JetBrains"] = "LSP") -> None:
|
||||
click.echo(f"\nSerena version: {serena_version()}\n")
|
||||
serena_config = SerenaConfig.from_config_file()
|
||||
serena_config.language_backend = LanguageBackend(language_backend)
|
||||
serena_config.save()
|
||||
click.echo(f"Configuration file: {serena_config.config_file_path}")
|
||||
click.echo(f"Language backend: {language_backend}")
|
||||
|
||||
# check for auto-configurable clients
|
||||
applicable_setup_handlers = []
|
||||
for setup_handler in client_setup_handlers:
|
||||
if setup_handler.is_applicable():
|
||||
applicable_setup_handlers.append(setup_handler)
|
||||
if len(applicable_setup_handlers) > 0:
|
||||
click.echo(
|
||||
"\nAuto-configurable clients detected.\nApply the following commands to configure the Serena MCP server (in a default configuration):"
|
||||
)
|
||||
for setup_handler in applicable_setup_handlers:
|
||||
click.echo(f" serena setup {setup_handler.name}")
|
||||
|
||||
click.echo("\nSerena has been initialised successfully.\n")
|
||||
|
||||
@staticmethod
|
||||
@click.command(
|
||||
"setup",
|
||||
help="Set up Serena for use with a specific client by registering it as an MCP server.",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.argument(
|
||||
"client",
|
||||
type=click.Choice([h.name for h in client_setup_handlers]),
|
||||
)
|
||||
def setup(client: str) -> None:
|
||||
# find the matching handler
|
||||
handler = next(h for h in client_setup_handlers if h.name == client)
|
||||
|
||||
# check applicability
|
||||
if not handler.is_applicable():
|
||||
click.echo(f"\nCannot apply setup for client '{client}' (not found or not functional).\n")
|
||||
raise SystemExit(1)
|
||||
|
||||
# apply the setup
|
||||
if handler.apply():
|
||||
click.echo(f"\nSerena has been successfully set up for {client}.\n")
|
||||
else:
|
||||
click.echo(f"\nFailed to set up Serena for {client}.\n")
|
||||
raise SystemExit(1)
|
||||
|
||||
@staticmethod
|
||||
@click.command("start-mcp-server", help="Starts the Serena MCP server.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@@ -1108,23 +1174,16 @@ class PromptCommands(AutoRegisteringGroup):
|
||||
click.echo(f"Deleted override file '{prompt_yaml_name}'.")
|
||||
|
||||
|
||||
# Expose groups so we can reference them in pyproject.toml
|
||||
mode = ModeCommands()
|
||||
context = ContextCommands()
|
||||
project = ProjectCommands()
|
||||
config = SerenaConfigCommands()
|
||||
tools = ToolCommands()
|
||||
prompts = PromptCommands()
|
||||
_mode = ModeCommands()
|
||||
_context = ContextCommands()
|
||||
_project = ProjectCommands()
|
||||
_config = SerenaConfigCommands()
|
||||
_tools = ToolCommands()
|
||||
_prompts = PromptCommands()
|
||||
|
||||
# Expose toplevel commands for the same reason
|
||||
# Expose so we can use this as an entrypoint
|
||||
top_level = TopLevelCommands()
|
||||
start_mcp_server = top_level.start_mcp_server
|
||||
|
||||
# needed for the help script to work - register all subcommands to the top-level group
|
||||
for subgroup in (mode, context, project, config, tools, prompts):
|
||||
for subgroup in (_mode, _context, _project, _config, _tools, _prompts):
|
||||
top_level.add_command(subgroup)
|
||||
|
||||
|
||||
def get_help() -> str:
|
||||
"""Retrieve the help text for the top-level Serena CLI."""
|
||||
return top_level.get_help(click.Context(top_level, info_name="serena"))
|
||||
+46
-17
@@ -57,6 +57,16 @@ class CodeEditor(Generic[TSymbol], ABC):
|
||||
"""
|
||||
raise NotImplementedError("This method must be overridden for each subclass")
|
||||
|
||||
def read_file(self, relative_path: str) -> str:
|
||||
"""
|
||||
Reads the content of a file.
|
||||
|
||||
:param relative_path: the relative path of the file to read
|
||||
:return: the content of the file
|
||||
"""
|
||||
with self._open_file_context(relative_path) as file:
|
||||
return file.get_contents()
|
||||
|
||||
@contextmanager
|
||||
def edited_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]:
|
||||
"""
|
||||
@@ -220,15 +230,8 @@ class CodeEditor(Generic[TSymbol], ABC):
|
||||
edited_file.delete_text_between_positions(start_pos, end_pos)
|
||||
|
||||
@abstractmethod
|
||||
def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> str:
|
||||
"""
|
||||
Renames the symbol with the given name throughout the codebase.
|
||||
|
||||
:param name_path: the name path of the symbol to rename
|
||||
:param relative_file_path: the relative path of the file containing the symbol
|
||||
:param new_name: the new name for the symbol
|
||||
:return: a status message
|
||||
"""
|
||||
def rename_symbol(self, name_path: str, relative_path: str, new_name: str) -> str:
|
||||
pass
|
||||
|
||||
|
||||
class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]):
|
||||
@@ -337,8 +340,16 @@ class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]):
|
||||
operation.apply()
|
||||
return len(operations)
|
||||
|
||||
def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> str:
|
||||
symbol = self._find_unique_symbol(name_path, relative_file_path)
|
||||
def rename_symbol(self, name_path: str, relative_path: str, new_name: str) -> str:
|
||||
"""
|
||||
Renames a symbol, file, or directory throughout the codebase.
|
||||
|
||||
:param name_path: the name path of the symbol to rename
|
||||
:param relative_path: the relative path of the file containing the symbol.
|
||||
:param new_name: the new name
|
||||
:return: a status message
|
||||
"""
|
||||
symbol = self._find_unique_symbol(name_path, relative_path)
|
||||
if not symbol.location.has_position_in_file():
|
||||
raise ValueError(f"Symbol '{name_path}' does not have a valid position in file for renaming")
|
||||
|
||||
@@ -346,9 +357,9 @@ class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]):
|
||||
assert symbol.location.line is not None
|
||||
assert symbol.location.column is not None
|
||||
|
||||
lang_server = self._get_language_server(relative_file_path)
|
||||
lang_server = self._get_language_server(relative_path)
|
||||
rename_result = lang_server.request_rename_symbol_edit(
|
||||
relative_file_path=relative_file_path, line=symbol.location.line, column=symbol.location.column, new_name=new_name
|
||||
relative_file_path=relative_path, line=symbol.location.line, column=symbol.location.column, new_name=new_name
|
||||
)
|
||||
if rename_result is None:
|
||||
raise ValueError(
|
||||
@@ -415,13 +426,31 @@ class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]):
|
||||
)
|
||||
return JetBrainsSymbol(symbols[0], self._project)
|
||||
|
||||
def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> str:
|
||||
def rename_symbol(
|
||||
self,
|
||||
name_path: str | None,
|
||||
relative_path: str,
|
||||
new_name: str,
|
||||
rename_in_comments: bool = False,
|
||||
rename_in_text_occurrences: bool = False,
|
||||
) -> str:
|
||||
"""
|
||||
Renames a code symbol, file, or directory throughout the codebase.
|
||||
|
||||
:param name_path: the name path of the symbol to rename. Set to None for renaming a file or directory.
|
||||
:param relative_path: if `name_path` is passed, the relative path of the file containing the symbol.
|
||||
Otherwise, the path to the directory or file to rename.
|
||||
:param new_name: the new name
|
||||
:param rename_in_comments: whether to rename occurrences of the symbol in comments
|
||||
:param rename_in_text_occurrences: whether to rename occurrences of the symbol in text
|
||||
:return: a status message
|
||||
"""
|
||||
with JetBrainsPluginClient.from_project(self._project) as client:
|
||||
client.rename_symbol(
|
||||
name_path=name_path,
|
||||
relative_path=relative_file_path,
|
||||
relative_path=relative_path,
|
||||
new_name=new_name,
|
||||
rename_in_comments=False,
|
||||
rename_in_text_occurrences=False,
|
||||
rename_in_comments=rename_in_comments,
|
||||
rename_in_text_occurrences=rename_in_text_occurrences,
|
||||
)
|
||||
return "Success"
|
||||
@@ -0,0 +1,91 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import click
|
||||
|
||||
from serena.util.shell import execute_shell_command
|
||||
|
||||
|
||||
class ClientSetupHandler(ABC):
|
||||
def __init__(self, name: str) -> None:
|
||||
self.name = name
|
||||
|
||||
@abstractmethod
|
||||
def is_applicable(self) -> bool:
|
||||
"""
|
||||
:return: whether the client setup can applied (respective client is available)
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_mcp_server_options(self) -> list[str]:
|
||||
pass
|
||||
|
||||
def get_mcp_server_command(self) -> str:
|
||||
return f"serena start-mcp-server {' '.join(self.get_mcp_server_options())}"
|
||||
|
||||
def _run_shell_command(self, cmd: str) -> bool:
|
||||
"""
|
||||
Runs the given shell command.
|
||||
If the command fails (i.e., with non-zero exit code), prints the stdout and stderr of the command for debugging.
|
||||
|
||||
:param cmd: the shell command to execute
|
||||
:return: whether the command executed successfully (i.e., with exit code 0)
|
||||
"""
|
||||
click.echo("Running command:")
|
||||
click.echo(cmd)
|
||||
result = execute_shell_command(cmd)
|
||||
is_success = result.return_code == 0
|
||||
if not is_success:
|
||||
if result.stdout:
|
||||
click.echo(result.stdout)
|
||||
if result.stderr:
|
||||
click.echo(result.stderr)
|
||||
return is_success
|
||||
|
||||
@abstractmethod
|
||||
def apply(self) -> bool:
|
||||
"""
|
||||
Applies the client setup
|
||||
"""
|
||||
|
||||
|
||||
class ClientSetupHandlerClaudeCode(ClientSetupHandler):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("claude-code")
|
||||
|
||||
def is_applicable(self) -> bool:
|
||||
result = execute_shell_command("claude --version")
|
||||
return result.return_code == 0 and "Claude" in result.stdout
|
||||
|
||||
def get_mcp_server_options(self) -> list[str]:
|
||||
return ["--context=claude-code", "--project-from-cwd"]
|
||||
|
||||
def apply(self) -> bool:
|
||||
cmd = f"claude mcp add --scope user serena -- {self.get_mcp_server_command()}"
|
||||
is_success = self._run_shell_command(cmd)
|
||||
if is_success:
|
||||
click.echo("\nIMPORTANT: We additionally recommend to set up hooks for Claude Code to ensure the best experience.")
|
||||
click.echo(" Please read the instructions here:")
|
||||
click.echo(" https://oraios.github.io/serena/02-usage/030_clients.html#claude-code")
|
||||
return is_success
|
||||
|
||||
|
||||
class ClientSetupHandlerCodex(ClientSetupHandler):
|
||||
"""
|
||||
Setup for Codex CLI and Codex App (shared config)
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__("codex")
|
||||
|
||||
def is_applicable(self) -> bool:
|
||||
result = execute_shell_command("codex --version")
|
||||
return result.return_code == 0 and "codex-cli" in result.stdout
|
||||
|
||||
def get_mcp_server_options(self) -> list[str]:
|
||||
return ["--context=codex", "--project-from-cwd"]
|
||||
|
||||
def apply(self) -> bool:
|
||||
return self._run_shell_command(f"codex mcp add serena -- {self.get_mcp_server_command()}")
|
||||
|
||||
|
||||
client_setup_handlers = [ClientSetupHandlerClaudeCode(), ClientSetupHandlerCodex()]
|
||||
@@ -53,7 +53,13 @@ class SerenaAgentMode(ToolInclusionDefinition, ToStringMixin):
|
||||
"""Print an overview of the mode."""
|
||||
print(f"{self.name}:\n {self.description}")
|
||||
if self.excluded_tools:
|
||||
print(" excluded tools:\n " + ", ".join(sorted(self.excluded_tools)))
|
||||
print(" excluded tools:\n " + ", ".join(sorted(self.excluded_tools)))
|
||||
if self.included_optional_tools:
|
||||
print(" included optional tools:\n " + ", ".join(sorted(self.included_optional_tools)))
|
||||
if self.fixed_tools:
|
||||
print(" fixed tools:\n " + ", ".join(sorted(self.fixed_tools)))
|
||||
if self.prompt:
|
||||
print(" defines initial prompt")
|
||||
|
||||
@classmethod
|
||||
def from_yaml(cls, yaml_path: str | Path) -> Self:
|
||||
|
||||
@@ -31,7 +31,7 @@ from serena.constants import (
|
||||
SERENA_MANAGED_DIR_NAME,
|
||||
)
|
||||
from serena.util.inspection import determine_programming_language_composition
|
||||
from serena.util.yaml import YamlCommentNormalisation, load_yaml, normalise_yaml_comments, save_yaml, transfer_missing_yaml_comments
|
||||
from serena.util.yaml import YamlCommentNormalisation, load_yaml, normalise_yaml_comments, save_yaml, transfer_yaml_comments
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
from ..analytics import RegisteredTokenCountEstimator
|
||||
@@ -83,10 +83,26 @@ class SerenaPaths:
|
||||
If a name of a mode matches a name of a mode in SERENAS_OWN_MODES_YAML_DIR,
|
||||
the user mode will override the default mode definition.
|
||||
"""
|
||||
self.news_snippet_id_file: str = os.path.join(self.serena_user_home_dir, "last_read_news_snippet_id.txt")
|
||||
self.news_legacy_last_read_id_file: str = os.path.join(self.serena_user_home_dir, "last_read_news_snippet_id.txt")
|
||||
"""
|
||||
file containing the ID of the last read news snippet
|
||||
"""
|
||||
self.news_read_items_file: str = os.path.join(self.serena_user_home_dir, "news_read.pkl")
|
||||
"""
|
||||
file containing the ID of the last read news snippet
|
||||
"""
|
||||
self.news_etag_file: str = os.path.join(self.serena_user_home_dir, "news_etag.txt")
|
||||
"""
|
||||
file containing the ETag of the last fetched remote news JSON
|
||||
"""
|
||||
self.news_file: str = os.path.join(self.serena_user_home_dir, "news.json")
|
||||
"""
|
||||
local cache of the remote news JSON file
|
||||
"""
|
||||
self.news_dir: str = os.path.join(REPO_ROOT, "news")
|
||||
"""
|
||||
repository news directory containing the source HTML snippets and generated news.json
|
||||
"""
|
||||
global_memories_path = Path(os.path.join(self.serena_user_home_dir, "memories", "global"))
|
||||
global_memories_path.mkdir(parents=True, exist_ok=True)
|
||||
self.global_memories_path = global_memories_path
|
||||
@@ -565,7 +581,7 @@ class ProjectConfig(SharedConfig):
|
||||
|
||||
# transfer missing comments from the template file
|
||||
template_config, _ = self._load_yaml_dict(PROJECT_TEMPLATE_FILE, self.YAML_COMMENT_NORMALISATION)
|
||||
transfer_missing_yaml_comments(template_config, config_with_comments, self.YAML_COMMENT_NORMALISATION, force_update_all=True)
|
||||
transfer_yaml_comments(template_config, config_with_comments, self.YAML_COMMENT_NORMALISATION, force_update_all=True)
|
||||
|
||||
# save project.yml
|
||||
save_yaml(config_path, config_with_comments)
|
||||
@@ -1047,7 +1063,7 @@ class SerenaConfig(SharedConfig):
|
||||
# For some keys, we force updates, because old comments are problematic/misleading.
|
||||
normalise_yaml_comments(commented_yaml, YamlCommentNormalisation.LEADING_WITH_CONVERSION_FROM_TRAILING)
|
||||
template_yaml = load_yaml(SERENA_CONFIG_TEMPLATE_FILE, comment_normalisation=YamlCommentNormalisation.LEADING)
|
||||
transfer_missing_yaml_comments(template_yaml, commented_yaml, YamlCommentNormalisation.LEADING, force_update_all=True)
|
||||
transfer_yaml_comments(template_yaml, commented_yaml, YamlCommentNormalisation.LEADING, force_update_all=True)
|
||||
|
||||
save_yaml(self.config_file_path, commented_yaml)
|
||||
|
||||
|
||||
+170
-64
@@ -5,23 +5,26 @@ import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from html import escape
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Self
|
||||
|
||||
import webview
|
||||
from flask import Flask, Response, redirect, request, send_from_directory
|
||||
from PIL import Image
|
||||
from pydantic import BaseModel
|
||||
from sensai.util import logging
|
||||
from sensai.util.pickle import dump_pickle, load_pickle
|
||||
|
||||
from serena.analytics import ToolUsageStats
|
||||
from serena.config.serena_config import SerenaConfig, SerenaPaths
|
||||
from serena.constants import SERENA_DASHBOARD_DIR, SerenaPorts
|
||||
from serena.task_executor import TaskExecutor
|
||||
from serena.util.logging import MemoryLogHandler
|
||||
from serena.util.pywebview import WebViewWithTray
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from serena.agent import SerenaAgent
|
||||
@@ -65,6 +68,7 @@ class ResponseConfigOverview(BaseModel):
|
||||
languages: list[str]
|
||||
encoding: str | None
|
||||
current_client: str | None
|
||||
serena_version: str
|
||||
|
||||
|
||||
class ResponseAvailableLanguages(BaseModel):
|
||||
@@ -132,6 +136,54 @@ class QueuedExecution(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class ReadNews:
|
||||
def __init__(self, read_ids: list[str], legacy_last_read_id: str | None = None):
|
||||
self._read_ids = set(read_ids)
|
||||
self._legacy_last_read_id = legacy_last_read_id
|
||||
|
||||
@staticmethod
|
||||
def load() -> "ReadNews":
|
||||
read_news_path = SerenaPaths().news_read_items_file
|
||||
legacy_last_read_id_path = SerenaPaths().news_legacy_last_read_id_file
|
||||
|
||||
def load_legacy_last_read_id() -> str | None:
|
||||
if not os.path.exists(legacy_last_read_id_path):
|
||||
return None
|
||||
with open(legacy_last_read_id_path, encoding="utf-8") as f:
|
||||
last_read_news_id = f.read().strip()
|
||||
if last_read_news_id == "20262103":
|
||||
last_read_news_id = "20260321" # fix originally misnamed news id
|
||||
return last_read_news_id
|
||||
|
||||
if os.path.exists(read_news_path):
|
||||
return load_pickle(read_news_path)
|
||||
else:
|
||||
instance = ReadNews(read_ids=[], legacy_last_read_id=load_legacy_last_read_id())
|
||||
instance._save()
|
||||
try:
|
||||
os.unlink(legacy_last_read_id_path)
|
||||
except:
|
||||
pass
|
||||
return instance
|
||||
|
||||
def _save(self) -> None:
|
||||
dump_pickle(self, SerenaPaths().news_read_items_file)
|
||||
|
||||
def is_read(self, identifier: str) -> bool:
|
||||
if identifier in self._read_ids:
|
||||
return True
|
||||
if self._legacy_last_read_id is not None and identifier <= self._legacy_last_read_id:
|
||||
return True
|
||||
return False
|
||||
|
||||
def mark_read(self, identifier: str) -> None:
|
||||
"""
|
||||
Marks the given news snippet as read, saving the new state to disk
|
||||
"""
|
||||
self._read_ids.add(identifier)
|
||||
self._save()
|
||||
|
||||
|
||||
class SerenaDashboardAPI:
|
||||
BASE_PORT = SerenaPorts.DASHBOARD_API_BASE_PORT
|
||||
|
||||
@@ -142,16 +194,19 @@ class SerenaDashboardAPI:
|
||||
memory_log_handler: MemoryLogHandler,
|
||||
tool_names: list[str],
|
||||
agent: "SerenaAgent",
|
||||
shutdown_callback: Callable[[], None] | None = None,
|
||||
tool_usage_stats: ToolUsageStats | None = None,
|
||||
) -> None:
|
||||
self._memory_log_handler = memory_log_handler
|
||||
self._tool_names = tool_names
|
||||
self._agent = agent
|
||||
self._shutdown_callback = shutdown_callback
|
||||
self._app = Flask(__name__)
|
||||
self._tool_usage_stats = tool_usage_stats
|
||||
self._loaded_news: dict[str, str] = {}
|
||||
self._news_ready = threading.Event()
|
||||
self._setup_routes()
|
||||
self._read_news = ReadNews.load()
|
||||
# Fetch remote news in background on startup (non-blocking)
|
||||
threading.Thread(target=self._fetch_news, daemon=True).start()
|
||||
|
||||
@property
|
||||
def memory_log_handler(self) -> MemoryLogHandler:
|
||||
@@ -220,7 +275,7 @@ class SerenaDashboardAPI:
|
||||
|
||||
@self._app.route("/shutdown", methods=["PUT"])
|
||||
def shutdown() -> dict[str, str]:
|
||||
self._shutdown()
|
||||
self._agent.shutdown()
|
||||
return {"status": "shutting down"}
|
||||
|
||||
@self._app.route("/get_available_languages", methods=["GET"])
|
||||
@@ -341,7 +396,7 @@ class SerenaDashboardAPI:
|
||||
return {
|
||||
"status": "success",
|
||||
"was_cancelled": False,
|
||||
"message": f"Task with id {request_data.get('task_id')} not found, maybe execution was already finished",
|
||||
"message": f"Task with id {escape(request_data.get('task_id'))} not found, maybe execution was already finished",
|
||||
}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e), "was_cancelled": False}
|
||||
@@ -355,36 +410,30 @@ class SerenaDashboardAPI:
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
@self._app.route("/news_snippet_ids", methods=["GET"])
|
||||
def get_news_snippet_ids() -> dict[str, str | list[int]]:
|
||||
def _get_unread_news_ids() -> list[int]:
|
||||
all_news_files = (Path(SERENA_DASHBOARD_DIR) / "news").glob("*.html")
|
||||
all_news_ids = [int(f.stem) for f in all_news_files]
|
||||
"""News ids are ints of format YYYYMMDD (publication dates)"""
|
||||
@self._app.route("/fetch_unread_news", methods=["GET"])
|
||||
def fetch_unread_news() -> dict[str, dict[str, str] | str]:
|
||||
def _fetch_unread_news() -> dict[str, str]:
|
||||
"""News ids are strings of format YYYYMMDD (publication dates)"""
|
||||
self._news_ready.wait()
|
||||
all_news = self._loaded_news
|
||||
|
||||
# Filter news items by installation date
|
||||
serena_config_creation_date = SerenaConfig.get_config_file_creation_date()
|
||||
if serena_config_creation_date is None:
|
||||
# should not normally happen, since config file should exist when the dashboard is started
|
||||
# We assume a fresh installation in this case
|
||||
log.error("Serena config file not found when starting the dashboard")
|
||||
return []
|
||||
serena_config_creation_date_int = int(serena_config_creation_date.strftime("%Y%m%d"))
|
||||
# Only include news items published on or after the installation date
|
||||
post_installation_news_ids = [news_id for news_id in all_news_ids if news_id >= serena_config_creation_date_int]
|
||||
return {}
|
||||
serena_config_creation_date = serena_config_creation_date.strftime("%Y%m%d")
|
||||
|
||||
news_snippet_id_file = SerenaPaths().news_snippet_id_file
|
||||
if not os.path.exists(news_snippet_id_file):
|
||||
return post_installation_news_ids
|
||||
with open(news_snippet_id_file, encoding="utf-8") as f:
|
||||
last_read_news_id = int(f.read().strip())
|
||||
if last_read_news_id == 20262103:
|
||||
last_read_news_id = 20260321 # fix originally misnamed file
|
||||
return [news_id for news_id in post_installation_news_ids if news_id > last_read_news_id]
|
||||
# filter for news after the installation date
|
||||
post_installation_news = {k: v for k, v in all_news.items() if k >= serena_config_creation_date}
|
||||
|
||||
# read unread news
|
||||
return {k: v for k, v in post_installation_news.items() if not self._read_news.is_read(k)}
|
||||
|
||||
try:
|
||||
unread_news_ids = _get_unread_news_ids()
|
||||
return {"news_snippet_ids": unread_news_ids, "status": "success"}
|
||||
unread_news = _fetch_unread_news()
|
||||
return {"news": unread_news, "status": "success"}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
@@ -392,10 +441,8 @@ class SerenaDashboardAPI:
|
||||
def mark_news_snippet_as_read() -> dict[str, str]:
|
||||
try:
|
||||
request_data = request.get_json()
|
||||
news_snippet_id = int(request_data.get("news_snippet_id"))
|
||||
news_snippet_id_file = SerenaPaths().news_snippet_id_file
|
||||
with open(news_snippet_id_file, "w", encoding="utf-8") as f:
|
||||
f.write(str(news_snippet_id))
|
||||
news_snippet_id = escape(str(request_data.get("news_snippet_id")))
|
||||
self._read_news.mark_read(news_snippet_id)
|
||||
return {"status": "success", "message": f"Marked news snippet {news_snippet_id} as read"}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
@@ -544,17 +591,9 @@ class SerenaDashboardAPI:
|
||||
languages=languages,
|
||||
encoding=encoding,
|
||||
current_client=Tool.get_last_tool_call_client_str(),
|
||||
serena_version=self._agent.version,
|
||||
)
|
||||
|
||||
def _shutdown(self) -> None:
|
||||
log.info("Shutting down Serena")
|
||||
if self._shutdown_callback:
|
||||
self._shutdown_callback()
|
||||
else:
|
||||
# noinspection PyProtectedMember
|
||||
# noinspection PyUnresolvedReferences
|
||||
os._exit(0)
|
||||
|
||||
def _get_available_languages(self) -> ResponseAvailableLanguages:
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
@@ -635,6 +674,66 @@ class SerenaDashboardAPI:
|
||||
|
||||
self._agent.execute_task(run, logged=True, name="SaveSerenaConfig")
|
||||
|
||||
# ===== Remote News Methods =====
|
||||
|
||||
# The branch from which news are fetched. Change to a feature branch for testing.
|
||||
_NEWS_JSON_URL = "https://oraios-software.de/serena_news.json"
|
||||
|
||||
def _fetch_news(self) -> None:
|
||||
"""Fetch news.json from GitHub using ETag-based caching and store in memory. Silently ignores network errors."""
|
||||
paths = SerenaPaths()
|
||||
|
||||
headers: dict[str, str] = {}
|
||||
# Load stored ETag if available
|
||||
if os.path.exists(paths.news_etag_file) and os.path.exists(paths.news_file):
|
||||
try:
|
||||
with open(paths.news_etag_file, encoding="utf-8") as f:
|
||||
stored_etag = f.read().strip()
|
||||
if stored_etag:
|
||||
headers["If-None-Match"] = stored_etag
|
||||
except Exception:
|
||||
log.warning("Failed to read stored news ETag at %s, proceeding without it", paths.news_etag_file, exc_info=True)
|
||||
|
||||
fetched_news_dict = None
|
||||
try:
|
||||
req = urllib.request.Request(self._NEWS_JSON_URL, headers=headers)
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
etag = response.headers.get("ETag", "")
|
||||
body = response.read().decode("utf-8")
|
||||
# Validate JSON
|
||||
fetched_news_dict = json.loads(body)
|
||||
# Store news content and ETag
|
||||
with open(paths.news_file, "w", encoding="utf-8") as f:
|
||||
f.write(body)
|
||||
if etag:
|
||||
with open(paths.news_etag_file, "w", encoding="utf-8") as f:
|
||||
f.write(etag)
|
||||
log.info("Remote news updated from %s", self._NEWS_JSON_URL)
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code == 304:
|
||||
log.debug("Remote news unchanged (304 Not Modified)")
|
||||
else:
|
||||
log.warning("Failed to fetch remote news (HTTP %d): %s", e.code, e.reason)
|
||||
except Exception as e:
|
||||
log.warning("Failed to fetch remote news: %s", e)
|
||||
if fetched_news_dict is None:
|
||||
fetched_news_dict = self._load_previously_fetched_news_data()
|
||||
self._loaded_news = fetched_news_dict
|
||||
self._news_ready.set()
|
||||
|
||||
@staticmethod
|
||||
def _load_previously_fetched_news_data() -> dict[str, str]:
|
||||
"""Return the news data dict. Uses local cache if available, otherwise falls back to local news files."""
|
||||
paths = SerenaPaths()
|
||||
|
||||
if os.path.exists(paths.news_file):
|
||||
try:
|
||||
with open(paths.news_file, encoding="utf-8") as f:
|
||||
return json.loads(f.read())
|
||||
except Exception:
|
||||
log.warning("Failed to read cached news data from %s", paths.news_file)
|
||||
return {}
|
||||
|
||||
def _add_language(self, request_add_language: RequestAddLanguage) -> None:
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
@@ -688,7 +787,7 @@ class SerenaDashboardAPI:
|
||||
return thread, port
|
||||
|
||||
|
||||
class SerenaDashboardViewer:
|
||||
class SerenaDashboardViewer(WebViewWithTray):
|
||||
"""
|
||||
Minimal pywebview wrapper that opens a dashboard in a native window.
|
||||
|
||||
@@ -696,17 +795,39 @@ class SerenaDashboardViewer:
|
||||
is handled separately by :class:`SerenaDashboardTrayManager`.
|
||||
"""
|
||||
|
||||
DEBUG = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
url: str,
|
||||
*,
|
||||
width: int = 1400,
|
||||
height: int = 900,
|
||||
start_minimized: bool = False,
|
||||
parent_process_id: int | None = None,
|
||||
tray: bool = True,
|
||||
):
|
||||
self.url = url
|
||||
self.title = "Serena Dashboard"
|
||||
self.width = width
|
||||
self.height = height
|
||||
dashboard_path = Path(SERENA_DASHBOARD_DIR)
|
||||
|
||||
# .ico is Windows-only; macOS expects a PNG for the window/dock icon.
|
||||
app_icon_filename = "serena.ico" if sys.platform == "win32" else "serena-icon-1024-mac.png"
|
||||
app_icon_path = str(dashboard_path / app_icon_filename)
|
||||
|
||||
tray_icon_filename = "serena-icon-tray-mac.png" if sys.platform == "darwin" else "serena-icon-48.png"
|
||||
tray_icon_path = str(dashboard_path / tray_icon_filename)
|
||||
|
||||
super().__init__(
|
||||
url,
|
||||
title="Serena Dashboard",
|
||||
tray=tray,
|
||||
width=width,
|
||||
height=height,
|
||||
start_minimized=start_minimized,
|
||||
parent_process_id=parent_process_id,
|
||||
app_id="oraios.serena",
|
||||
app_icon_path=app_icon_path,
|
||||
tray_icon_path=tray_icon_path,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def is_current_platform_supported() -> bool:
|
||||
@@ -722,26 +843,11 @@ class SerenaDashboardViewer:
|
||||
return sys.platform in supported_platforms
|
||||
|
||||
def run(self) -> None:
|
||||
# set app id (avoid app being lumped together with other Python-based apps in Windows taskbar)
|
||||
if sys.platform == "win32":
|
||||
import ctypes
|
||||
if self.DEBUG:
|
||||
logging.configure(level=logging.DEBUG)
|
||||
logging.add_file_logger(SerenaPaths().get_next_log_file_path("dashboard-viewer"))
|
||||
|
||||
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("oraios.serena")
|
||||
|
||||
dashboard_path = Path(SERENA_DASHBOARD_DIR)
|
||||
# .ico is Windows-only; macOS expects a PNG for the window/dock icon.
|
||||
icon_filename = "serena.ico" if sys.platform == "win32" else "serena-icon-1024-mac.png"
|
||||
icon_path = str(dashboard_path / icon_filename)
|
||||
|
||||
window = webview.create_window(
|
||||
self.title,
|
||||
self.url,
|
||||
width=self.width,
|
||||
height=self.height,
|
||||
)
|
||||
assert window is not None
|
||||
|
||||
webview.start(icon=icon_path)
|
||||
super().run()
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -892,7 +998,7 @@ class SerenaDashboardTrayManager:
|
||||
@staticmethod
|
||||
def _run_viewer(url: str) -> None:
|
||||
"""Process target for running a dashboard viewer window."""
|
||||
SerenaDashboardViewer(url).run()
|
||||
SerenaDashboardViewer(url, tray=False, start_minimized=False).run()
|
||||
|
||||
def _alive_check_loop(self) -> None:
|
||||
"""Periodically check whether registered instances are still reachable.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# mypy: ignore-errors
|
||||
import logging
|
||||
import os
|
||||
import queue
|
||||
import sys
|
||||
import threading
|
||||
import tkinter as tk
|
||||
import traceback
|
||||
from collections.abc import Callable
|
||||
from enum import Enum, auto
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
@@ -38,6 +38,7 @@ class GuiLogViewer:
|
||||
memory_log_handler: MemoryLogHandler | None = None,
|
||||
width=800,
|
||||
height=600,
|
||||
shutdown_handler: Callable[[], None] | None = None,
|
||||
):
|
||||
"""
|
||||
:param mode: the mode; if "dashboard", run a dashboard with logs and some control options; if "error", run
|
||||
@@ -57,6 +58,7 @@ class GuiLogViewer:
|
||||
self.log_thread = None
|
||||
self.menubar: tk.Menu | None = None
|
||||
self.tool_names = [] # List to store tool names for highlighting
|
||||
self.shutdown_handler = shutdown_handler
|
||||
|
||||
# Define colors for different log levels
|
||||
self.log_colors = {
|
||||
@@ -320,10 +322,8 @@ class GuiLogViewer:
|
||||
self.running = False
|
||||
|
||||
def _shutdown_server(self) -> None:
|
||||
log.info("Shutting down Serena")
|
||||
# noinspection PyUnresolvedReferences
|
||||
# noinspection PyProtectedMember
|
||||
os._exit(0)
|
||||
if self.shutdown_handler is not None:
|
||||
self.shutdown_handler()
|
||||
|
||||
|
||||
class GuiLogViewerHandler(logging.Handler):
|
||||
|
||||
@@ -0,0 +1,435 @@
|
||||
import json
|
||||
import os
|
||||
import pickle
|
||||
import shutil
|
||||
import sys
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Literal, Self
|
||||
|
||||
import click
|
||||
|
||||
from serena.util.cli_util import AutoRegisteringGroup
|
||||
|
||||
# copied from serena_config.py, we don't want to import anything here to keep the hook commands fast
|
||||
serena_home_dir = os.getenv("SERENA_HOME", "").strip() or str(Path.home() / ".serena")
|
||||
|
||||
|
||||
class HookClient(Enum):
|
||||
"""The client application that triggered the hook."""
|
||||
|
||||
CLAUDE_CODE = "claude-code"
|
||||
VSCODE = "vscode"
|
||||
CODEX = "codex"
|
||||
|
||||
|
||||
class Hook(ABC):
|
||||
def __init__(self, client: HookClient):
|
||||
raw = sys.stdin.read()
|
||||
input_data = json.loads(raw)
|
||||
self._input_data = input_data
|
||||
self._client = client
|
||||
|
||||
session_id = input_data.get("session_id") or input_data.get("sessionId")
|
||||
if not session_id:
|
||||
raise ValueError("Session ID is required in the hook input data")
|
||||
self._session_id = str(session_id)
|
||||
self.session_persistence_dir = os.path.join(serena_home_dir, "hook_data", self._session_id)
|
||||
# tool input has a timestamp but using now is enough
|
||||
self.triggered_at_timestamp = datetime.now()
|
||||
|
||||
@abstractmethod
|
||||
def execute(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class PreToolUseHook(Hook, ABC):
|
||||
def __init__(self, client: HookClient):
|
||||
super().__init__(client)
|
||||
_tool_name = self._input_data.get("tool_name") or self._input_data.get("toolName", "") or ""
|
||||
_tool_name = str(_tool_name).lower().strip()
|
||||
if not _tool_name:
|
||||
raise ValueError("Tool name is required in the hook input data")
|
||||
self._tool_name = _tool_name
|
||||
self._tool_input: dict | None = self._input_data.get("tool_input") or self._input_data.get("toolInput")
|
||||
|
||||
# only relevant in claude code at the moment, (not all events include this field; default to empty string)
|
||||
raw_permission_mode = self._input_data.get("permission_mode") or self._input_data.get("permissionMode") or ""
|
||||
self._permission_mode = str(raw_permission_mode).strip()
|
||||
|
||||
@dataclass
|
||||
class OutputData:
|
||||
permission_decision: Literal["deny", "allow"]
|
||||
permission_decision_reason: str
|
||||
additional_context: str = ""
|
||||
|
||||
def to_json_string(self) -> str:
|
||||
hook_output = {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": self.permission_decision,
|
||||
"permissionDecisionReason": self.permission_decision_reason,
|
||||
"additionalContext": self.additional_context,
|
||||
}
|
||||
}
|
||||
return json.dumps(hook_output)
|
||||
|
||||
def is_serena_tool(self) -> bool:
|
||||
return "serena" in self._tool_name
|
||||
|
||||
|
||||
class PreToolUseRemindAboutSerenaHook(PreToolUseHook):
|
||||
"""Pre-tool-use hook that nudges the agent toward Serena's symbolic tools.
|
||||
|
||||
Tracks consecutive uses of grep and read-file tools via a persisted
|
||||
:class:`ToolUseCounter`. When the number of recent calls reaches the
|
||||
configured threshold, a deny response is emitted with a reminder to
|
||||
use symbolic alternatives.
|
||||
|
||||
The counter for a given tool type is reset whenever
|
||||
|
||||
* a Serena tool is invoked (both counters are reset),
|
||||
* a deny is emitted (the acting counter is reset so the next retry starts fresh),
|
||||
* or the configured reset period elapses *between two consecutive calls of that
|
||||
same tool type* — i.e. the period gates the gap between successive calls, not
|
||||
an absolute sliding window. Three grep calls at t=0, t=9, t=18 therefore count
|
||||
as a burst of three, even though the total span (18s) exceeds the 10s grep
|
||||
period; only an individual pair that is more than 10s apart resets the counter.
|
||||
|
||||
Non-tracked tools (Edit, Write, Bash, etc.) are deliberately neutral: they neither
|
||||
increment nor reset counters, so they also do not mask bursts by pushing the last
|
||||
timestamp forward.
|
||||
|
||||
The hook is additionally gated by :attr:`ToolUseCounter._MIN_DENY_INTERVAL_SECONDS`
|
||||
(two minutes by default): once a deny has been emitted, *every* subsequent
|
||||
invocation of this hook is a no-op until the window has elapsed — neither the
|
||||
counters are updated nor any further deny is produced. This prevents the agent
|
||||
from being nudged more than once per window during a sustained non-symbolic-tool
|
||||
burst, and also avoids surprising the user with reminders that were already
|
||||
counted up under stale state.
|
||||
"""
|
||||
|
||||
@dataclass
|
||||
class ToolUseCounter:
|
||||
_FILE_NAME = "tool_use_counter.pkl"
|
||||
_GREP_USES_THRESHOLD = 3
|
||||
_READ_FILE_USES_THRESHOLD = 3
|
||||
# threshold for the combined "non-symbolic" counter that catches mixed sequences of grep+read
|
||||
_NON_SYMBOLIC_USES_THRESHOLD = 4
|
||||
|
||||
# The following periods are set to essentially infinity since we neglect the per-tool reset periods for them
|
||||
_READ_FILE_RESET_PERIOD_SECONDS = 1000
|
||||
_GREP_RESET_PERIOD_SECONDS = 1000
|
||||
# reset period for the combined counter
|
||||
_NON_SYMBOLIC_RESET_PERIOD_SECONDS = 2000
|
||||
|
||||
# minimum seconds between two engagements of the hook after a deny; the entire
|
||||
# hook (counter updates included) is a no-op while this window is active, so a
|
||||
# single sustained burst triggers at most one nudge per window
|
||||
_MIN_DENY_INTERVAL_SECONDS = 120
|
||||
|
||||
n_recent_read_file_uses: int = 0
|
||||
n_recent_grep_uses: int = 0
|
||||
n_recent_non_symbolic_uses: int = 0
|
||||
last_grep_use_timestamp: datetime | None = None
|
||||
last_read_file_use_timestamp: datetime | None = None
|
||||
last_non_symbolic_use_timestamp: datetime | None = None
|
||||
# timestamp of the most recently emitted deny; deliberately not cleared by
|
||||
# :meth:`reset` so the rate limit survives counter resets (e.g. Serena tool use)
|
||||
last_deny_timestamp: datetime | None = None
|
||||
|
||||
def too_many_recent_reads(self) -> bool:
|
||||
return self.n_recent_read_file_uses >= self._READ_FILE_USES_THRESHOLD
|
||||
|
||||
def too_many_recent_greps(self) -> bool:
|
||||
return self.n_recent_grep_uses >= self._GREP_USES_THRESHOLD
|
||||
|
||||
def too_many_recent_non_symbolic(self) -> bool:
|
||||
return self.n_recent_non_symbolic_uses >= self._NON_SYMBOLIC_USES_THRESHOLD
|
||||
|
||||
def is_hook_active(self, now: datetime) -> bool:
|
||||
""":return: whether the hook should engage at all at ``now``. Returns
|
||||
``False`` while we are still within :attr:`_MIN_DENY_INTERVAL_SECONDS`
|
||||
of the most recent emitted deny — in that case the entire hook is
|
||||
short-circuited (no counter updates, no deny). Returns ``True`` when
|
||||
no deny has been emitted yet in this session, or when the window has
|
||||
elapsed.
|
||||
"""
|
||||
if self.last_deny_timestamp is None:
|
||||
return True
|
||||
return (now - self.last_deny_timestamp).total_seconds() >= self._MIN_DENY_INTERVAL_SECONDS
|
||||
|
||||
@classmethod
|
||||
def _get_persistence_path(cls, hook: Hook) -> Path:
|
||||
return Path(hook.session_persistence_dir) / cls._FILE_NAME
|
||||
|
||||
@classmethod
|
||||
def load(cls, hook: Hook) -> Self:
|
||||
path = cls._get_persistence_path(hook)
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
return pickle.load(f)
|
||||
except Exception:
|
||||
return cls()
|
||||
|
||||
def save(self, hook: Hook) -> None:
|
||||
path = self._get_persistence_path(hook)
|
||||
try:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(path, "wb") as f:
|
||||
pickle.dump(self, f)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def update(self, hook: "PreToolUseRemindAboutSerenaHook") -> None:
|
||||
if hook.is_serena_tool():
|
||||
self.reset()
|
||||
return
|
||||
|
||||
now = hook.triggered_at_timestamp
|
||||
is_grep = hook.is_grep_tool()
|
||||
is_read = hook.is_read_file_tool()
|
||||
|
||||
# update grep counter
|
||||
if is_grep:
|
||||
grep_period = self._GREP_RESET_PERIOD_SECONDS
|
||||
if self.last_grep_use_timestamp is not None and (now - self.last_grep_use_timestamp).total_seconds() <= grep_period:
|
||||
self.n_recent_grep_uses += 1
|
||||
else:
|
||||
self.n_recent_grep_uses = 1
|
||||
self.last_grep_use_timestamp = now
|
||||
|
||||
# update read file counter
|
||||
if is_read:
|
||||
read_period = self._READ_FILE_RESET_PERIOD_SECONDS
|
||||
if (
|
||||
self.last_read_file_use_timestamp is not None
|
||||
and (now - self.last_read_file_use_timestamp).total_seconds() <= read_period
|
||||
):
|
||||
self.n_recent_read_file_uses += 1
|
||||
else:
|
||||
self.n_recent_read_file_uses = 1
|
||||
self.last_read_file_use_timestamp = now
|
||||
|
||||
# update combined non-symbolic counter — catches mixed bursts (e.g.
|
||||
# alternating grep/read) that neither per-tool counter would trip
|
||||
if is_grep or is_read:
|
||||
combined_period = self._NON_SYMBOLIC_RESET_PERIOD_SECONDS
|
||||
if (
|
||||
self.last_non_symbolic_use_timestamp is not None
|
||||
and (now - self.last_non_symbolic_use_timestamp).total_seconds() <= combined_period
|
||||
):
|
||||
self.n_recent_non_symbolic_uses += 1
|
||||
else:
|
||||
self.n_recent_non_symbolic_uses = 1
|
||||
self.last_non_symbolic_use_timestamp = now
|
||||
|
||||
def reset(self) -> None:
|
||||
self.n_recent_read_file_uses = 0
|
||||
self.n_recent_grep_uses = 0
|
||||
self.n_recent_non_symbolic_uses = 0
|
||||
self.last_grep_use_timestamp = None
|
||||
self.last_read_file_use_timestamp = None
|
||||
self.last_non_symbolic_use_timestamp = None
|
||||
|
||||
#: substrings that, combined with ``"file"`` in the tool name, identify a
|
||||
#: read-file tool for non-Claude-Code clients (whose tool names vary across
|
||||
#: editors/agents). Lowercase because :attr:`_tool_name` is lowercased on
|
||||
#: ingest. Conservative on purpose: only verbs that strongly imply *reading*
|
||||
#: a file, never *modifying* one — so ``view_file``/``open_file``/``show_file``
|
||||
#: are caught alongside ``read_file``, while ``write_file``/``edit_file`` are not.
|
||||
_READ_FILE_VERB_SUBSTRINGS: tuple[str, ...] = ("read", "view", "open", "show")
|
||||
|
||||
def __init__(self, client: HookClient):
|
||||
super().__init__(client)
|
||||
self._tool_call_counter = self.ToolUseCounter.load(self)
|
||||
|
||||
def is_grep_tool(self) -> bool:
|
||||
if self._client == HookClient.CLAUDE_CODE:
|
||||
return self._tool_name == "grep"
|
||||
return "grep" in self._tool_name
|
||||
|
||||
def is_read_file_tool(self) -> bool:
|
||||
if self._client == HookClient.CLAUDE_CODE:
|
||||
return self._tool_name == "read"
|
||||
name = self._tool_name
|
||||
if "file" not in name:
|
||||
return False
|
||||
return any(verb in name for verb in self._READ_FILE_VERB_SUBSTRINGS)
|
||||
|
||||
def execute(self) -> None:
|
||||
# gate the entire hook on the rate-limit window: while we are within
|
||||
# _MIN_DENY_INTERVAL_SECONDS of the last emitted deny, no counter
|
||||
# updates and no deny detection happen at all. The pickle is left
|
||||
# untouched so state survives the gating window unchanged.
|
||||
if not self._tool_call_counter.is_hook_active(self.triggered_at_timestamp):
|
||||
return
|
||||
|
||||
self._tool_call_counter.update(self)
|
||||
|
||||
# pick the deny that matches the current call first, so the emitted message lines up
|
||||
# with the tool the agent just invoked; fall back to the other per-tool counter only
|
||||
# if the current call did not itself trip a threshold (e.g. stale state loaded from
|
||||
# pickle). The combined non-symbolic deny is checked last — it only fires when neither
|
||||
# per-tool counter tripped, which is exactly the mixed-burst case (alternating grep/read).
|
||||
too_many_greps = self._tool_call_counter.too_many_recent_greps()
|
||||
too_many_reads = self._tool_call_counter.too_many_recent_reads()
|
||||
too_many_non_symbolic = self._tool_call_counter.too_many_recent_non_symbolic()
|
||||
|
||||
output_data: PreToolUseHook.OutputData | None = None
|
||||
if self.is_grep_tool() and too_many_greps:
|
||||
output_data = self._build_grep_deny()
|
||||
elif self.is_read_file_tool() and too_many_reads:
|
||||
output_data = self._build_read_deny()
|
||||
elif too_many_greps:
|
||||
output_data = self._build_grep_deny()
|
||||
elif too_many_reads:
|
||||
output_data = self._build_read_deny()
|
||||
elif too_many_non_symbolic:
|
||||
output_data = self._build_non_symbolic_deny()
|
||||
|
||||
if output_data is not None:
|
||||
# reset burst counters so the next interval starts fresh, then record
|
||||
# the deny timestamp and emit. The is_hook_active() guard at the top
|
||||
# ensures we never reach this branch within the rate-limit window.
|
||||
self._tool_call_counter.reset()
|
||||
self._tool_call_counter.last_deny_timestamp = self.triggered_at_timestamp
|
||||
click.echo(output_data.to_json_string())
|
||||
self._tool_call_counter.save(self)
|
||||
|
||||
def _build_grep_deny(self) -> "PreToolUseHook.OutputData":
|
||||
return self.OutputData(
|
||||
permission_decision="deny",
|
||||
permission_decision_reason="Too many consecutive grep calls without using symbolic tools. "
|
||||
"You can continue using grep now if needed, the counter was reset.",
|
||||
additional_context=(
|
||||
"You were using many grep calls recently. Consider using Serena's symbolic "
|
||||
"mcp tools instead for more code-centric search. You can continue using grep now if needed, the counter was reset."
|
||||
),
|
||||
)
|
||||
|
||||
def _build_read_deny(self) -> "PreToolUseHook.OutputData":
|
||||
return self.OutputData(
|
||||
permission_decision="deny",
|
||||
permission_decision_reason="Too many consecutive read calls without using symbolic tools. "
|
||||
"You can continue using read now if needed, the counter was reset.",
|
||||
additional_context=(
|
||||
"You were using many read file calls recently. Consider using Serena's symbolic "
|
||||
"mcp tools instead for more targeted reads. You can continue using read now if needed, the counter was reset."
|
||||
),
|
||||
)
|
||||
|
||||
def _build_non_symbolic_deny(self) -> "PreToolUseHook.OutputData":
|
||||
return self.OutputData(
|
||||
permission_decision="deny",
|
||||
permission_decision_reason="Too many consecutive non-symbolic tool calls (mixed grep and read). "
|
||||
"You can continue using these tools now if needed, the counter was reset.",
|
||||
additional_context=(
|
||||
"You were alternating between grep and read file calls recently without using "
|
||||
"Serena's symbolic mcp tools. Consider using symbolic search and targeted symbol "
|
||||
"reads instead for more code-centric exploration. You can continue using these tools "
|
||||
"now if needed, the counter was reset."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class SessionStartActivateProjectHook(Hook):
|
||||
def execute(self) -> None:
|
||||
message = (
|
||||
"**IMPORTANT**: Activate the current working directory as project using Serena's tools if not already done. "
|
||||
"If you haven't read Serena Instructions Manual yet, do so. "
|
||||
"Follow this instruction before doing anything else."
|
||||
)
|
||||
result = {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "SessionStart",
|
||||
"additionalContext": message,
|
||||
}
|
||||
}
|
||||
click.echo(json.dumps(result))
|
||||
|
||||
|
||||
class SessionEndCleanupHook(Hook):
|
||||
def execute(self) -> None:
|
||||
shutil.rmtree(self.session_persistence_dir, ignore_errors=True)
|
||||
|
||||
|
||||
class PreToolUseAutoApproveSerenaHook(PreToolUseHook):
|
||||
"""Pre-tool-use hook that auto-approves Serena tool calls while the client is in accept-edits mode.
|
||||
|
||||
Claude Code's ``acceptEdits`` permission mode only applies to its built-in editing tools;
|
||||
Serena's destructive tools (e.g. ``replace_symbol_body`` or ``rename_symbol``) would still
|
||||
prompt the user on every call. This hook emits an ``allow`` decision for any Serena MCP
|
||||
tool call whenever the client reports ``acceptEdits`` as the active permission mode, so
|
||||
blanket edit approvals also cover Serena's tools. In all other situations it stays silent,
|
||||
preserving the default approval flow.
|
||||
"""
|
||||
|
||||
_ACCEPT_EDITS_MODE = "acceptEdits"
|
||||
|
||||
def is_accept_edits_mode(self) -> bool:
|
||||
return self._permission_mode == self._ACCEPT_EDITS_MODE
|
||||
|
||||
def execute(self) -> None:
|
||||
# only emit a decision when both the tool and the mode match; stay silent otherwise
|
||||
if not self.is_serena_tool() or not self.is_accept_edits_mode():
|
||||
return
|
||||
|
||||
output_data = self.OutputData(
|
||||
permission_decision="allow",
|
||||
permission_decision_reason="Auto-approved: Serena tool call while client is in acceptEdits mode.",
|
||||
)
|
||||
click.echo(output_data.to_json_string())
|
||||
|
||||
|
||||
_client_option = click.option(
|
||||
"--client",
|
||||
type=click.Choice([e.value for e in HookClient], case_sensitive=False),
|
||||
default=HookClient.CLAUDE_CODE.value,
|
||||
show_default=True,
|
||||
help="The client application that triggered the hook.",
|
||||
)
|
||||
|
||||
|
||||
class HookCommands(AutoRegisteringGroup):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(name="serena-hook", help="Commands that send reminders to agents when appropriate, to be used in hooks.")
|
||||
|
||||
@staticmethod
|
||||
@click.command(
|
||||
"activate",
|
||||
help="Set this as hook at session startup to prompt the agent to activate the project at the start of the session and read Serena's instructions",
|
||||
)
|
||||
@_client_option
|
||||
def activate(client: str) -> None:
|
||||
SessionStartActivateProjectHook(HookClient(client)).execute()
|
||||
|
||||
@staticmethod
|
||||
@click.command("cleanup", help="Set this as hook at session end all hook data for the current session")
|
||||
@_client_option
|
||||
def cleanup(client: str) -> None:
|
||||
SessionEndCleanupHook(HookClient(client)).execute()
|
||||
|
||||
@staticmethod
|
||||
@click.command(
|
||||
"remind",
|
||||
help="Set this as hook at PreToolUse to remind the agent to use Serena's tools instead of overrelying on read_file and grep",
|
||||
)
|
||||
@_client_option
|
||||
def remind(client: str) -> None:
|
||||
PreToolUseRemindAboutSerenaHook(HookClient(client)).execute()
|
||||
|
||||
@staticmethod
|
||||
@click.command(
|
||||
"auto-approve",
|
||||
help="Set this as hook at PreToolUse to auto-approve Serena tool calls while the client is in acceptEdits mode (Claude Code).",
|
||||
)
|
||||
@_client_option
|
||||
def auto_approve(client: str) -> None:
|
||||
PreToolUseAutoApproveSerenaHook(HookClient(client)).execute()
|
||||
|
||||
|
||||
hook_commands = HookCommands()
|
||||
@@ -426,6 +426,22 @@ class JetBrainsPluginClient(ToStringMixin):
|
||||
self._postprocess_symbol_collection_response(symbol_collection)
|
||||
return symbol_collection
|
||||
|
||||
def move(
|
||||
self,
|
||||
name_path: str | None,
|
||||
relative_path: str | None,
|
||||
target_parent_name_path: str | None,
|
||||
target_relative_path: str | None,
|
||||
) -> dict[str, Any]:
|
||||
self._require_version_at_least(2023, 2, 14)
|
||||
request_data = {
|
||||
"namePath": name_path,
|
||||
"relativePath": relative_path,
|
||||
"targetParentNamePath": target_parent_name_path,
|
||||
"targetRelativePath": target_relative_path,
|
||||
}
|
||||
return self._make_request("POST", "/moveSymbol", request_data)
|
||||
|
||||
def find_references(self, name_path: str, relative_path: str, include_quick_info: bool) -> jb.SymbolCollectionResponse:
|
||||
"""
|
||||
Finds references to a symbol.
|
||||
@@ -505,8 +521,46 @@ class JetBrainsPluginClient(ToStringMixin):
|
||||
}
|
||||
return cast(jb.TypeHierarchyResponse, self._make_request("POST", "/getSubtypes", request_data))
|
||||
|
||||
def safe_delete(self, name_path: str | None, relative_path: str, delete_even_if_used: bool, propagate: bool) -> dict[str, Any]:
|
||||
"""
|
||||
Safely deletes a symbol, checking for usages first.
|
||||
|
||||
:param name_path: the name path of the symbol to delete
|
||||
:param relative_path: the relative path to the file containing the symbol
|
||||
:param delete_even_if_used: if True, delete the symbol even if it has usages
|
||||
"""
|
||||
self._require_version_at_least(2023, 2, 14)
|
||||
request_data = {
|
||||
"namePath": name_path,
|
||||
"relativePath": relative_path,
|
||||
"deleteEvenIfUsed": delete_even_if_used,
|
||||
"propagate": propagate,
|
||||
}
|
||||
return self._make_request("POST", "/safeDelete", request_data)
|
||||
|
||||
def inline_symbol(
|
||||
self,
|
||||
name_path: str,
|
||||
relative_path: str,
|
||||
keep_definition: bool,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Inlines a method, replacing all call sites with the method body.
|
||||
|
||||
:param name_path: the name path of the method to inline
|
||||
:param relative_path: the relative path to the file containing the method
|
||||
:param keep_definition: if True, keep the original method definition after inlining
|
||||
"""
|
||||
self._require_version_at_least(2023, 2, 14)
|
||||
request_data = {
|
||||
"namePath": name_path,
|
||||
"relativePath": relative_path,
|
||||
"keepDefinition": keep_definition,
|
||||
}
|
||||
return self._make_request("POST", "/inlineSymbol", request_data)
|
||||
|
||||
def rename_symbol(
|
||||
self, name_path: str, relative_path: str, new_name: str, rename_in_comments: bool, rename_in_text_occurrences: bool
|
||||
self, name_path: str | None, relative_path: str, new_name: str, rename_in_comments: bool, rename_in_text_occurrences: bool
|
||||
) -> None:
|
||||
"""
|
||||
Renames a symbol.
|
||||
@@ -537,6 +591,48 @@ class JetBrainsPluginClient(ToStringMixin):
|
||||
}
|
||||
self._make_request("POST", "/refreshFile", request_data)
|
||||
|
||||
def find_declaration(
|
||||
self, relative_path: str, line: int, col: int, include_body: bool, include_quick_info: bool
|
||||
) -> jb.SymbolCollectionResponse:
|
||||
"""
|
||||
Finds the declaration of the symbol at the given location.
|
||||
|
||||
:param relative_path: the relative path to the file
|
||||
:param line: the line number (0-based)
|
||||
:param col: the column number (0-based)
|
||||
:param include_body: whether to include the symbol body
|
||||
:param include_quick_info: whether to include quick info about the symbol
|
||||
"""
|
||||
self._require_version_at_least(2023, 2, 14)
|
||||
request_data = {
|
||||
"relativePath": relative_path,
|
||||
"line": line,
|
||||
"col": col,
|
||||
"includeBody": include_body,
|
||||
"includeQuickInfo": include_quick_info,
|
||||
}
|
||||
symbol_collection = cast(jb.SymbolCollectionResponse, self._make_request("POST", "/findDeclaration", request_data))
|
||||
self._postprocess_symbol_collection_response(symbol_collection)
|
||||
return symbol_collection
|
||||
|
||||
def find_implementations(self, relative_path: str, name_path: str, include_quick_info: bool) -> jb.SymbolCollectionResponse:
|
||||
"""
|
||||
Finds the implementations of a symbol.
|
||||
|
||||
:param relative_path: the relative path to the file containing the symbol
|
||||
:param name_path: the name path of the symbol
|
||||
:param include_quick_info: whether to include quick info about the symbol
|
||||
"""
|
||||
self._require_version_at_least(2023, 2, 14)
|
||||
request_data = {
|
||||
"relativePath": relative_path,
|
||||
"namePath": name_path,
|
||||
"includeQuickInfo": include_quick_info,
|
||||
}
|
||||
symbol_collection = cast(jb.SymbolCollectionResponse, self._make_request("POST", "/findImplementations", request_data))
|
||||
self._postprocess_symbol_collection_response(symbol_collection)
|
||||
return symbol_collection
|
||||
|
||||
def close(self) -> None:
|
||||
self._session.close()
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
from typing import Literal, NotRequired, TypedDict
|
||||
|
||||
JB_EXTERNAL_FILE_PREFIX = "<ext:"
|
||||
"""
|
||||
Prefix used for in relative paths of symbols that are from external libraries (i.e., not defined in the user's codebase).
|
||||
"""
|
||||
|
||||
|
||||
class PluginStatusDTO(TypedDict):
|
||||
project_root: str
|
||||
@@ -22,15 +27,48 @@ class SymbolDTO(TypedDict):
|
||||
type: str
|
||||
body: NotRequired[str]
|
||||
quick_info: NotRequired[str]
|
||||
"""Quick info text (e.g., type signature) for the symbol, as HTML string."""
|
||||
"""quick info text (e.g., type signature) for the symbol, as HTML string."""
|
||||
documentation: NotRequired[str]
|
||||
"""Documentation text for the symbol (if available), as HTML string."""
|
||||
"""documentation text for the symbol (if available), as HTML string."""
|
||||
text_range: NotRequired[TextRangeDTO]
|
||||
children: NotRequired[list["SymbolDTO"]]
|
||||
num_usages: NotRequired[int]
|
||||
reference_line_no: NotRequired[int]
|
||||
"""
|
||||
for the case where this is a reference, the line number of the reference (0-based, relative to the file where the symbol is defined).
|
||||
"""
|
||||
|
||||
# --- Python-side extensions, i.e. fields that are not returned by the plugin but set later by the Python code ---
|
||||
|
||||
context: NotRequired[str]
|
||||
"""
|
||||
context around the symbol/reference, e.g., a few lines of code before and after the symbol definition or reference,
|
||||
to provide additional context for the LLM.
|
||||
"""
|
||||
|
||||
|
||||
SymbolDTOKey = Literal["name_path", "relative_path", "type", "body", "quick_info", "documentation", "text_range", "children", "num_usages"]
|
||||
SymbolDTOKey = Literal[
|
||||
"name_path",
|
||||
"relative_path",
|
||||
"type",
|
||||
"body",
|
||||
"quick_info",
|
||||
"documentation",
|
||||
"text_range",
|
||||
"children",
|
||||
"num_usages",
|
||||
"reference_line_no",
|
||||
"context",
|
||||
]
|
||||
|
||||
|
||||
class SymbolDTOUtil:
|
||||
@staticmethod
|
||||
def is_external_symbol(symbol_dto: SymbolDTO) -> bool:
|
||||
"""
|
||||
Checks if a symbol is an external symbol (i.e., from a library) based on its relative path.
|
||||
"""
|
||||
return symbol_dto["relative_path"].startswith(JB_EXTERNAL_FILE_PREFIX)
|
||||
|
||||
|
||||
class SymbolCollectionResponse(TypedDict):
|
||||
|
||||
+9
-2
@@ -332,7 +332,14 @@ class SerenaMCPFactory:
|
||||
# retain only FASTMCP_ prefix for already set environment variables.
|
||||
Settings.model_config = SettingsConfigDict(env_prefix="FASTMCP_")
|
||||
instructions = self._get_initial_instructions()
|
||||
mcp = FastMCP(lifespan=self.server_lifespan, host=host, port=port, instructions=instructions)
|
||||
mcp = FastMCP(
|
||||
name="Serena",
|
||||
lifespan=self.server_lifespan,
|
||||
website_url="https://oraios.github.io/serena",
|
||||
host=host,
|
||||
port=port,
|
||||
instructions=instructions,
|
||||
)
|
||||
return mcp
|
||||
|
||||
@asynccontextmanager
|
||||
@@ -346,7 +353,7 @@ class SerenaMCPFactory:
|
||||
finally:
|
||||
log.info("MCP server shutting down")
|
||||
if self.agent is not None:
|
||||
self.agent.shutdown()
|
||||
self.agent.on_shutdown()
|
||||
|
||||
def _get_initial_instructions(self) -> str:
|
||||
assert self.agent is not None
|
||||
|
||||
+2
-23
@@ -1,4 +1,3 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
@@ -291,7 +290,7 @@ class Project(ToStringMixin):
|
||||
|
||||
self.language_server_manager: LanguageServerManager | None = None
|
||||
self._language_server_manager_init_error: Exception | None = None
|
||||
self._is_newly_created = is_newly_created
|
||||
self.is_newly_created = is_newly_created
|
||||
self._agent: Optional["SerenaAgent"] = None
|
||||
|
||||
# create .gitignore file in the project's Serena data folder if not yet present
|
||||
@@ -379,26 +378,6 @@ class Project(ToStringMixin):
|
||||
def path_to_project_yml(self) -> str:
|
||||
return self.serena_config.get_project_yml_location(self.project_root)
|
||||
|
||||
def get_activation_message(self) -> str:
|
||||
"""
|
||||
:return: a message providing information about the project upon activation (e.g. programming language, memories, initial prompt)
|
||||
"""
|
||||
if self._is_newly_created:
|
||||
msg = f"Created and activated a new project with name '{self.project_name}' at {self.project_root}. "
|
||||
else:
|
||||
msg = f"The project with name '{self.project_name}' at {self.project_root} is activated."
|
||||
languages_str = ", ".join([lang.value for lang in self.project_config.languages])
|
||||
msg += f"\nProgramming languages: {languages_str}; file encoding: {self.project_config.encoding}"
|
||||
project_memories = self.memories_manager.list_project_memories()
|
||||
if project_memories:
|
||||
msg += (
|
||||
f"\nAvailable project memories: {json.dumps(project_memories.to_dict())}\n"
|
||||
+ "Use the `read_memory` tool to read these memories later if they are relevant to the task."
|
||||
)
|
||||
if self.project_config.initial_prompt:
|
||||
msg += f"\nAdditional project-specific instructions:\n {self.project_config.initial_prompt}"
|
||||
return msg
|
||||
|
||||
def read_file(self, relative_path: str) -> str:
|
||||
"""
|
||||
Reads a file relative to the project root.
|
||||
@@ -469,7 +448,7 @@ class Project(ToStringMixin):
|
||||
rel_path = Path(relative_path)
|
||||
|
||||
# always ignore paths inside .git
|
||||
if len(rel_path.parts) > 0 and rel_path.parts[0] == ".git":
|
||||
if len(rel_path.parts) > 0 and ".git" in rel_path.parts:
|
||||
return True
|
||||
|
||||
return match_path(str(relative_path), self._ignore_spec, root_path=self.project_root)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
description: Generic IDE coding agent context (basic file operations and shell operations assumed to be covered; single project mode)
|
||||
prompt: |
|
||||
You are running in an IDE assistant context where file operations, basic (line-based) edits and reads,
|
||||
and shell commands are handled by your own, internal tools.
|
||||
|
||||
If Serena's tools can be used to achieve your task, you should prioritize them.
|
||||
In particular, it is important that you avoid reading entire source code files unless it is strictly necessary!
|
||||
Instead, for exploring and reading code in a token-efficient manner, use Serena's overview and symbolic search tools.
|
||||
For non-code files or for reads where you don't know the symbol's name path, you can use the pattern search tool.
|
||||
|
||||
excluded_tools:
|
||||
- create_text_file
|
||||
- read_file
|
||||
- execute_shell_command
|
||||
- find_file
|
||||
- list_dir
|
||||
|
||||
tool_description_overrides: {}
|
||||
|
||||
# whether to assume that Serena shall only work on a single project in this context (provided that a project is given
|
||||
# when Serena is started).
|
||||
# If set to true and a project is provided at startup, the set of tools is limited to those required by the project's
|
||||
# concrete configuration, and other tools are excluded completely, allowing the set of tools to be minimal.
|
||||
# Tools explicitly disabled by the project will not be available at all.
|
||||
# The `activate_project` tool is always disabled in this case, as project switching cannot be allowed.
|
||||
single_project: true
|
||||
@@ -11,8 +11,7 @@ prompt: |
|
||||
a diagram in addition to your text-based communication. Note that in the chat interface you have various rendering
|
||||
options for text, html, and mermaid diagrams, as has been explained to you in your initial instructions.
|
||||
excluded_tools: []
|
||||
included_optional_tools:
|
||||
- switch_modes
|
||||
included_optional_tools: []
|
||||
|
||||
tool_description_overrides:
|
||||
find_symbol: |
|
||||
|
||||
@@ -4,16 +4,22 @@ prompt: |
|
||||
as well as shell commands are handled by your own, internal tools.
|
||||
|
||||
If Serena's tools can be used to achieve your task, you should prioritize them.
|
||||
In particular, it is important that you avoid reading entire source code files unless it is strictly necessary!
|
||||
Instead, for exploring and reading code in a token-efficient manner, use Serena's overview and symbolic search tools.
|
||||
For non-code files or for reads where you don't know the symbol's name path, you can use the pattern search tool.
|
||||
**IMPORTANT**: If you haven't seen Serena's tools yet, you should use the tool search tool to load all of them
|
||||
right now.
|
||||
For exploring and reading code in a token-efficient manner, use Serena's code-centric overview and symbolic search tools.
|
||||
For edits, where it makes sense, use Serena's editing tools since they are more token efficient.
|
||||
Use your own editing tools only if Serena's editing tools would be suboptimal.
|
||||
**IMPORTANT**: Never use a tool before reading its schema via the tool-search tool!
|
||||
|
||||
|
||||
excluded_tools:
|
||||
- create_text_file
|
||||
- read_file
|
||||
- execute_shell_command
|
||||
- prepare_for_new_conversation
|
||||
- replace_content
|
||||
- find_file
|
||||
- list_dir
|
||||
- search_for_pattern
|
||||
|
||||
tool_description_overrides: {}
|
||||
|
||||
|
||||
@@ -13,8 +13,9 @@ excluded_tools:
|
||||
- create_text_file
|
||||
- read_file
|
||||
- execute_shell_command
|
||||
- prepare_for_new_conversation
|
||||
- replace_content
|
||||
- find_file
|
||||
- list_dir
|
||||
|
||||
|
||||
tool_description_overrides: {}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
description: Claude Code (CLI agent where file operations, basic edits, etc. are already covered; single project mode)
|
||||
prompt: |
|
||||
You are running in a CLI coding agent context where file operations, basic (line-based) edits and reads
|
||||
as well as shell commands are handled by your own, internal tools.
|
||||
|
||||
If Serena's tools can be used to achieve your task, you should prioritize them.
|
||||
In particular, it is important that you avoid reading entire source code files unless it is strictly necessary!
|
||||
Instead, for exploring and reading code in a token-efficient manner, use Serena's overview and symbolic search tools.
|
||||
For non-code files or for reads where you don't know the symbol's name path, you can use the pattern search tool.
|
||||
|
||||
excluded_tools:
|
||||
- create_text_file
|
||||
- read_file
|
||||
- execute_shell_command
|
||||
- replace_content
|
||||
|
||||
tool_description_overrides: {}
|
||||
|
||||
# whether to assume that Serena shall only work on a single project in this context (provided that a project is given
|
||||
# when Serena is started).
|
||||
# If set to true and a project is provided at startup, the set of tools is limited to those required by the project's
|
||||
# concrete configuration, and other tools are excluded completely, allowing the set of tools to be minimal.
|
||||
# Tools explicitly disabled by the project will not be available at all.
|
||||
# The `activate_project` tool is always disabled in this case, as project switching cannot be allowed.
|
||||
single_project: true
|
||||
@@ -9,7 +9,6 @@ prompt: |
|
||||
To illustrate complex relationships, consider creating diagrams in addition to your text-based communication
|
||||
(depending on the options for text, html, mermaid diagrams, etc. that you are provided with in your initial instructions).
|
||||
excluded_tools: []
|
||||
included_optional_tools:
|
||||
- switch_modes
|
||||
included_optional_tools: []
|
||||
|
||||
tool_description_overrides: {}
|
||||
@@ -12,7 +12,8 @@ excluded_tools:
|
||||
- create_text_file
|
||||
- read_file
|
||||
- execute_shell_command
|
||||
- prepare_for_new_conversation
|
||||
- find_file
|
||||
- list_dir
|
||||
|
||||
tool_description_overrides: {}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
description: Generic IDE coding agent context (basic file operations and shell operations assumed to be covered; single project mode)
|
||||
prompt: |
|
||||
You are running in an IDE assistant context where file operations, basic (line-based) edits and reads,
|
||||
and shell commands are handled by your own, internal tools.
|
||||
|
||||
If Serena's tools can be used to achieve your task, you should prioritize them.
|
||||
In particular, it is important that you avoid reading entire source code files unless it is strictly necessary!
|
||||
Instead, for exploring and reading code in a token-efficient manner, use Serena's overview and symbolic search tools.
|
||||
For non-code files or for reads where you don't know the symbol's name path, you can use the pattern search tool.
|
||||
|
||||
excluded_tools:
|
||||
- create_text_file
|
||||
- read_file
|
||||
- execute_shell_command
|
||||
- find_file
|
||||
- list_dir
|
||||
|
||||
tool_description_overrides: {}
|
||||
|
||||
# whether to assume that Serena shall only work on a single project in this context (provided that a project is given
|
||||
# when Serena is started).
|
||||
# If set to true and a project is provided at startup, the set of tools is limited to those required by the project's
|
||||
# concrete configuration, and other tools are excluded completely, allowing the set of tools to be minimal.
|
||||
# Tools explicitly disabled by the project will not be available at all.
|
||||
# The `activate_project` tool is always disabled in this case, as project switching cannot be allowed.
|
||||
single_project: true
|
||||
@@ -0,0 +1,27 @@
|
||||
description: Generic IDE coding agent context (basic file operations and shell operations assumed to be covered; single project mode)
|
||||
prompt: |
|
||||
In addition to Serena's tools, you have access to internal tools, including for, basic (line-based) edits and reads,
|
||||
and shell commands.
|
||||
|
||||
If Serena's tools can be used to achieve your task, you should prioritize them.
|
||||
In particular, it is important that you avoid reading entire source code files unless it is strictly necessary!
|
||||
Instead, for exploring and reading code in a token-efficient manner, use Serena's overview and symbolic search tools.
|
||||
For non-code files or for reads where you don't know the symbol's name path, you can use the pattern search tool.
|
||||
|
||||
Make sure to first activate the working directory as using Serena's `activate_project` tool and to read initial
|
||||
instructions (if not done so already) with the `initial_instructions` tool.
|
||||
|
||||
excluded_tools:
|
||||
- create_text_file
|
||||
- read_file
|
||||
- execute_shell_command
|
||||
|
||||
tool_description_overrides: {}
|
||||
|
||||
# whether to assume that Serena shall only work on a single project in this context (provided that a project is given
|
||||
# when Serena is started).
|
||||
# If set to true and a project is provided at startup, the set of tools is limited to those required by the project's
|
||||
# concrete configuration, and other tools are excluded completely, allowing the set of tools to be minimal.
|
||||
# Tools explicitly disabled by the project will not be available at all.
|
||||
# The `activate_project` tool is always disabled in this case, as project switching cannot be allowed.
|
||||
single_project: true
|
||||
@@ -0,0 +1,24 @@
|
||||
description: Generic IDE coding agent context (basic file operations and shell operations assumed to be covered; single project mode)
|
||||
prompt: |
|
||||
You are running in an IDE assistant context where file operations, basic (line-based) edits and reads,
|
||||
and shell commands are handled by your own, internal tools.
|
||||
|
||||
If Serena's tools can be used to achieve your task, you should prioritize them.
|
||||
In particular, it is important that you avoid reading entire source code files unless it is strictly necessary!
|
||||
Instead, for exploring and reading code in a token-efficient manner, use Serena's overview and symbolic search tools.
|
||||
For non-code files or for reads where you don't know the symbol's name path, you can use the pattern search tool.
|
||||
|
||||
excluded_tools:
|
||||
- create_text_file
|
||||
- read_file
|
||||
- execute_shell_command
|
||||
|
||||
tool_description_overrides: {}
|
||||
|
||||
# whether to assume that Serena shall only work on a single project in this context (provided that a project is given
|
||||
# when Serena is started).
|
||||
# If set to true and a project is provided at startup, the set of tools is limited to those required by the project's
|
||||
# concrete configuration, and other tools are excluded completely, allowing the set of tools to be minimal.
|
||||
# Tools explicitly disabled by the project will not be available at all.
|
||||
# The `activate_project` tool is always disabled in this case, as project switching cannot be allowed.
|
||||
single_project: true
|
||||
@@ -0,0 +1,29 @@
|
||||
description: Generic IDE coding agent context (basic file operations and shell operations assumed to be covered; single project mode)
|
||||
prompt: |
|
||||
In addition to Serena's tools, you have access to internal tools, including for, basic (line-based) edits and reads,
|
||||
and shell commands.
|
||||
|
||||
If Serena's tools can be used to achieve your task, you should prioritize them.
|
||||
In particular, it is important that you avoid reading entire source code files unless it is strictly necessary!
|
||||
Instead, for exploring and reading code in a token-efficient manner, use Serena's overview and symbolic search tools.
|
||||
For non-code files or for reads where you don't know the symbol's name path, you can use the pattern search tool.
|
||||
|
||||
Make sure to first activate the working directory as using Serena's `activate_project` tool and to read initial
|
||||
instructions (if not done so already) with the `initial_instructions` tool.
|
||||
|
||||
excluded_tools:
|
||||
- create_text_file
|
||||
- read_file
|
||||
- execute_shell_command
|
||||
- find_file
|
||||
- list_dir
|
||||
|
||||
tool_description_overrides: {}
|
||||
|
||||
# whether to assume that Serena shall only work on a single project in this context (provided that a project is given
|
||||
# when Serena is started).
|
||||
# If set to true and a project is provided at startup, the set of tools is limited to those required by the project's
|
||||
# concrete configuration, and other tools are excluded completely, allowing the set of tools to be minimal.
|
||||
# Tools explicitly disabled by the project will not be available at all.
|
||||
# The `activate_project` tool is always disabled in this case, as project switching cannot be allowed.
|
||||
single_project: true
|
||||
@@ -1,6 +1,7 @@
|
||||
description: JetBrains tools replace language server-based tools
|
||||
prompt: |
|
||||
You have access to the very powerful JetBrains tools for symbolic operations:
|
||||
You have access to the very powerful JetBrains tools for symbolic operations.
|
||||
These party replace regular tools you were informed about:
|
||||
* `jet_brains_find_symbol` replaces `find_symbol`
|
||||
* `jet_brains_find_referencing_symbols` replaces `find_referencing_symbols`
|
||||
* `jet_brains_get_symbols_overview` replaces `get_symbols_overview`
|
||||
@@ -9,8 +10,16 @@ excluded_tools:
|
||||
- find_referencing_symbols
|
||||
- get_symbols_overview
|
||||
- restart_language_server
|
||||
- safe_delete_symbol
|
||||
- rename_symbol
|
||||
included_optional_tools:
|
||||
- jet_brains_find_symbol
|
||||
- jet_brains_find_declaration
|
||||
- jet_brains_find_implementations
|
||||
- jet_brains_find_referencing_symbols
|
||||
- jet_brains_find_symbol
|
||||
- jet_brains_get_symbols_overview
|
||||
- jet_brains_inline_symbol
|
||||
- jet_brains_move
|
||||
- jet_brains_type_hierarchy
|
||||
- jet_brains_rename
|
||||
- jet_brains_safe_delete
|
||||
@@ -1,15 +1,18 @@
|
||||
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.
|
||||
|
||||
prompt: |
|
||||
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.
|
||||
|
||||
{% if 'jet_brains_move' in available_tools -%}
|
||||
**Refactoring tools**
|
||||
You also have access to Serena's refactoring tools (move, inline, safe_delete)
|
||||
which should ALWAYS be preferred over naive and primitive approaches when applicable.
|
||||
When you need to move, inline or delete a symbol, you should always try using the corresponding tool first.
|
||||
{%- endif %}
|
||||
|
||||
{% if 'jet_brains_move' in available_tools -%}
|
||||
Apart from refactoring tools
|
||||
{%- endif %}
|
||||
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.
|
||||
|
||||
|
||||
@@ -34,42 +34,3 @@ prompts:
|
||||
Moreover, you should create memory files for the style and conventions and a dedicated memory file for
|
||||
what should be done when a task is completed.
|
||||
**Important**: after done with the onboarding task, remember to call the `write_memory` to save the collected information!
|
||||
|
||||
think_about_collected_information: |
|
||||
Have you collected all the information you need for solving the current task? If not, can the missing information be acquired by using the available tools,
|
||||
in particular the tools related to symbol discovery? Or do you need to ask the user for more information?
|
||||
Think about it step by step and give a summary of the missing information and how it could be acquired.
|
||||
|
||||
think_about_task_adherence: |
|
||||
Are you deviating from the task at hand? Do you need any additional information to proceed?
|
||||
Have you loaded all relevant memory files to see whether your implementation is fully aligned with the
|
||||
code style, conventions, and guidelines of the project? If not, adjust your implementation accordingly
|
||||
before modifying any code into the codebase.
|
||||
Note that it is better to stop and ask the user for clarification
|
||||
than to perform large changes which might not be aligned with the user's intentions.
|
||||
If you feel like the conversation is deviating too much from the original task, apologize and suggest to the user
|
||||
how to proceed. If the conversation became too long, create a summary of the current progress and suggest to the user
|
||||
to start a new conversation based on that summary.
|
||||
|
||||
think_about_whether_you_are_done: |
|
||||
Have you already performed all the steps required by the task? Is it appropriate to run tests and linting, and if so,
|
||||
have you done that already? Is it appropriate to adjust non-code files like documentation and config and have you done that already?
|
||||
Should new tests be written to cover the changes?
|
||||
Note that a task that is just about exploring the codebase does not require running tests or linting.
|
||||
Read the corresponding memory files to see what should be done when a task is completed.
|
||||
|
||||
summarize_changes: |
|
||||
Summarize all the changes you have made to the codebase over the course of the conversation.
|
||||
Explore the diff if needed (e.g. by using `git diff`) to ensure that you have not missed anything.
|
||||
Explain whether and how the changes are covered by tests. Explain how to best use the new code, how to understand it,
|
||||
which existing code it affects and interacts with. Are there any dangers (like potential breaking changes or potential new problems)
|
||||
that the user should be aware of? Should any new documentation be written or existing documentation updated?
|
||||
You can use tools to explore the codebase prior to writing the summary, but don't write any new code in this step until
|
||||
the summary is complete.
|
||||
|
||||
prepare_for_new_conversation: |
|
||||
You have not yet completed the current task but we are running out of context.
|
||||
{mode_prepare_for_new_conversation}
|
||||
Imagine that you are handing over the task to another person who has access to the
|
||||
same tools and memory files as you do, but has not been part of the conversation so far.
|
||||
Write a summary that can be used in the next conversation to a memory file using the `write_memory` tool.
|
||||
@@ -3,7 +3,6 @@
|
||||
# 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.
|
||||
@@ -34,7 +33,7 @@ prompts:
|
||||
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.
|
||||
|
||||
@@ -637,6 +637,8 @@ class Dashboard {
|
||||
|
||||
let html = '<div class="config-grid">';
|
||||
|
||||
html += '<div class="config-label">Version:</div><div class="config-value">' + config.serena_version + '</div>';
|
||||
|
||||
// Project info
|
||||
html += '<div class="config-label">Active Project:</div>';
|
||||
if (config.active_project.name && config.active_project.path) {
|
||||
@@ -702,10 +704,6 @@ class Dashboard {
|
||||
html += '<div class="config-label">File Encoding:</div>';
|
||||
html += '<div class="config-value">' + (config.encoding || 'N/A') + '</div>';
|
||||
|
||||
// Current Client info
|
||||
html += '<div class="config-label">Current Client:</div>';
|
||||
html += '<div class="config-value">' + (config.current_client || 'None') + '</div>';
|
||||
|
||||
html += '</div>';
|
||||
|
||||
// Active tools - collapsible
|
||||
@@ -2073,31 +2071,36 @@ class Dashboard {
|
||||
let self = this;
|
||||
console.log('Loading news...');
|
||||
$.ajax({
|
||||
url: '/news_snippet_ids',
|
||||
url: '/fetch_unread_news',
|
||||
type: 'GET',
|
||||
success: function(response) {
|
||||
console.log('News snippet IDs response:', response);
|
||||
if (response.status === 'success' && response.news_snippet_ids && response.news_snippet_ids.length > 0) {
|
||||
console.log('Displaying news with IDs:', response.news_snippet_ids);
|
||||
self.displayNews(response.news_snippet_ids);
|
||||
console.log('Unread news response:', response);
|
||||
if (response.status === 'success' && response.news && Object.keys(response.news).length > 0) {
|
||||
const newsIds = Object.keys(response.news);
|
||||
self.displayNews(newsIds, response.news);
|
||||
} else {
|
||||
console.log('No unread news, hiding section');
|
||||
self.$newsSection.hide();
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error loading news snippet IDs:', error);
|
||||
console.error('Error loading news:', error);
|
||||
self.$newsSection.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
displayNews(newsIds) {
|
||||
/**
|
||||
* Display news items given unread IDs and the full news data mapping.
|
||||
* @param {number[]} newsIds - array of unread news IDs
|
||||
* @param {Object} newsData - mapping of news ID strings to HTML content
|
||||
*/
|
||||
displayNews(newsIds, newsData) {
|
||||
let self = this;
|
||||
console.log('displayNews called with:', newsIds);
|
||||
// Sort newest first (descending order)
|
||||
newsIds.sort((a, b) => b - a);
|
||||
|
||||
|
||||
if (newsIds.length === 0) {
|
||||
console.log('No news items to display.');
|
||||
self.$newsSection.hide();
|
||||
@@ -2106,40 +2109,32 @@ class Dashboard {
|
||||
self.$newsSection.show();
|
||||
self.$newsDisplay.empty();
|
||||
console.log('Displaying ' + newsIds.length + ' news items.');
|
||||
// Load each news snippet HTML
|
||||
let loadedCount = 0;
|
||||
newsIds.forEach(function(newsId) {
|
||||
$.ajax({
|
||||
url: '/dashboard/news/' + newsId + '.html',
|
||||
type: 'GET',
|
||||
success: function(html) {
|
||||
// Wrap the HTML in a container with a button
|
||||
let $newsContainer = $('<div class="news-container">').attr('data-news-id', newsId);
|
||||
let $newsContent = $(html);
|
||||
|
||||
// Add button for marking as read
|
||||
let $markRead = $('<div class="news-mark-read">');
|
||||
let $button = $('<button class="news-mark-read-btn">').attr('data-news-id', newsId).text('Mark as read');
|
||||
|
||||
$markRead.append($button);
|
||||
$newsContent.append($markRead);
|
||||
|
||||
$newsContainer.append($newsContent);
|
||||
self.$newsDisplay.append($newsContainer);
|
||||
|
||||
// Bind button click event
|
||||
$button.on('click', function() {
|
||||
const btn = $(this);
|
||||
btn.prop('disabled', true).text('Marking...');
|
||||
self.markNewsAsRead(newsId);
|
||||
});
|
||||
|
||||
loadedCount++;
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error loading news snippet ' + newsId + ':', error);
|
||||
loadedCount++;
|
||||
}
|
||||
newsIds.forEach(function(newsId) {
|
||||
const html = newsData[String(newsId)];
|
||||
if (!html) {
|
||||
console.warn('No news content found for ID ' + newsId);
|
||||
return;
|
||||
}
|
||||
// Wrap the HTML in a container with a button
|
||||
let $newsContainer = $('<div class="news-container">').attr('data-news-id', newsId);
|
||||
let $newsContent = $(html);
|
||||
|
||||
// Add button for marking as read
|
||||
let $markRead = $('<div class="news-mark-read">');
|
||||
let $button = $('<button class="news-mark-read-btn">').attr('data-news-id', newsId).text('Mark as read');
|
||||
|
||||
$markRead.append($button);
|
||||
$newsContent.append($markRead);
|
||||
|
||||
$newsContainer.append($newsContent);
|
||||
self.$newsDisplay.append($newsContainer);
|
||||
|
||||
// Bind button click event
|
||||
$button.on('click', function() {
|
||||
const btn = $(this);
|
||||
btn.prop('disabled', true).text('Marking...');
|
||||
self.markNewsAsRead(newsId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ project_name: "project_name"
|
||||
# al bash clojure cpp csharp
|
||||
# csharp_omnisharp dart elixir elm erlang
|
||||
# fortran fsharp go groovy haskell
|
||||
# java julia kotlin lua markdown
|
||||
# haxe java julia kotlin lua
|
||||
# markdown
|
||||
# matlab nix pascal perl php
|
||||
# php_phpactor powershell python python_jedi r
|
||||
# rego ruby ruby_solargraph rust scala
|
||||
@@ -69,40 +70,41 @@ read_only: false
|
||||
# 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.
|
||||
# * `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.
|
||||
# * `delete_memory`: 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.
|
||||
# * `edit_memory`: Replaces content matching a regular expression in a memory.
|
||||
# * `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).
|
||||
# * `find_file`: Finds files in the given relative paths
|
||||
# * `find_referencing_symbols`: Finds symbols that reference the given symbol using the language server backend
|
||||
# * `find_symbol`: Performs a global (or local) search using the language server backend.
|
||||
# * `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.
|
||||
# * `initial_instructions`: Provides instructions Serena usage (i.e. the 'Serena Instructions Manual')
|
||||
# for clients that do not read the initial instructions when the MCP server is connected.
|
||||
# * `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.
|
||||
# * `list_memories`: List available memories. Any memory can be read using the `read_memory` tool.
|
||||
# * `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.
|
||||
# * `read_memory`: 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.
|
||||
# * `rename_memory`: Renames or moves a memory. Moving between project and global scope is supported
|
||||
# (e.g., renaming "global/foo" to "bar" moves it from global to project scope).
|
||||
# * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities.
|
||||
# For JB, we use a separate tool.
|
||||
# * `replace_content`: Replaces content in a file (optionally using regular expressions).
|
||||
# * `replace_symbol_body`: Replaces the full definition of a symbol using the language server backend.
|
||||
# * `safe_delete_symbol`:
|
||||
# * `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.
|
||||
# * `write_memory`: 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.
|
||||
excluded_tools: []
|
||||
|
||||
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
|
||||
|
||||
@@ -80,7 +80,7 @@ class TaskExecutor:
|
||||
|
||||
:param timeout: the maximum time to wait in seconds; if None, use the task's own timeout
|
||||
(which may be None to wait indefinitely)
|
||||
:return: True if the task is done, False if the timeout was reached
|
||||
:return: the result of the task
|
||||
"""
|
||||
return self.future.result(timeout=timeout)
|
||||
|
||||
|
||||
@@ -28,8 +28,11 @@ class ActivateProjectTool(Tool, ToolMarkerDoesNotRequireActiveProject):
|
||||
|
||||
: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()
|
||||
is_new_activation = self.agent.activate_project_from_path_or_name(project)
|
||||
if not is_new_activation:
|
||||
result = "Project was already active."
|
||||
else:
|
||||
result = self.agent.get_project_activation_message()
|
||||
result += "\nIMPORTANT: If you have not yet read the 'Serena Instructions Manual', do it now before continuing!"
|
||||
return result
|
||||
|
||||
@@ -49,27 +52,6 @@ class RemoveProjectTool(Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerO
|
||||
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
|
||||
"""
|
||||
self.agent.set_modes(modes)
|
||||
|
||||
# Inform the Agent about the activated modes and the currently active tools
|
||||
mode_instances = self.agent.get_active_modes()
|
||||
result_str = f"Active 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.
|
||||
|
||||
@@ -3,10 +3,12 @@ from collections import Counter
|
||||
from typing import Any, Literal
|
||||
|
||||
import serena.jetbrains.jetbrains_types as jb
|
||||
from serena.code_editor import JetBrainsCodeEditor
|
||||
from serena.jetbrains.jetbrains_plugin_client import JetBrainsPluginClient
|
||||
from serena.jetbrains.jetbrains_types import SymbolDTO
|
||||
from serena.jetbrains.jetbrains_types import SymbolDTO, SymbolDTOUtil
|
||||
from serena.symbol import JetBrainsSymbolDictGrouper
|
||||
from serena.tools import Tool, ToolMarkerOptional, ToolMarkerSymbolicRead
|
||||
from serena.tools import Tool, ToolMarkerBeta, ToolMarkerOptional, ToolMarkerSymbolicEdit, ToolMarkerSymbolicRead
|
||||
from serena.util.text_utils import find_text_coordinates
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -56,6 +58,8 @@ class JetBrainsFindSymbolTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional):
|
||||
for the case where the symbol is a class, this will return its methods).
|
||||
Ignored if `include_body=True`. Default 0.
|
||||
:param relative_path: Optional. Restrict search to this file or directory. If not specified, searches entire codebase.
|
||||
Note: for external dependencies, this must be an identifier starting with `<ext` that you have received
|
||||
earlier (don't try to guess!).
|
||||
:param include_body: If True, include the symbol's source code. Use judiciously.
|
||||
:param include_info: whether to include additional info (hover-like, typically including docstring and signature),
|
||||
about the symbol.
|
||||
@@ -69,8 +73,16 @@ class JetBrainsFindSymbolTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional):
|
||||
if include_body:
|
||||
depth = 0 # ignore user-specified depth if body is requested
|
||||
|
||||
name_path_pattern = self._sanitize_input_param(name_path_pattern)
|
||||
|
||||
if relative_path:
|
||||
relative_path = self._sanitize_input_param(relative_path)
|
||||
if relative_path == ".":
|
||||
relative_path = None
|
||||
|
||||
if relative_path is not None and relative_path.startswith(jb.JB_EXTERNAL_FILE_PREFIX):
|
||||
search_deps = True
|
||||
|
||||
with JetBrainsPluginClient.from_project(self.project) as client:
|
||||
if include_body:
|
||||
include_quick_info = False
|
||||
@@ -111,6 +123,129 @@ class JetBrainsFindSymbolTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional):
|
||||
return self._limit_length(result, max_answer_chars, shortened_result_factories=[create_shortened_result])
|
||||
|
||||
|
||||
class JetBrainsMoveTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional, ToolMarkerBeta):
|
||||
"""
|
||||
Moves a symbol, file or directory to a new location using the JetBrains backend, updating all references
|
||||
"""
|
||||
|
||||
def apply(
|
||||
self,
|
||||
relative_path: str,
|
||||
name_path: str | None = None,
|
||||
target_relative_path: str | None = None,
|
||||
target_parent_name_path: str | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
Moves a symbol, file or directory to a different location and automatically update all references to affected symbols.
|
||||
**Important**: this tool should always be preferred to naive moving (e.g. via file system operations or edits)
|
||||
as it is much more reliable and efficient. It is always safe to use this tool. For some symbols, moving may not be applicable,
|
||||
and will result in no edits and a suitable error message.
|
||||
The target location is the new parent of the symbol,
|
||||
i.e. the moved entity is never renamed by the operation, only moved.
|
||||
|
||||
|
||||
Valid moves:
|
||||
- Symbol:
|
||||
* (relative_path, name_path) -> new parent symbol (target_relative_path, target_parent_name_path)
|
||||
* (relative_path, name_path) -> top level of target file or directory (target_relative_path)
|
||||
Always consider the concrete language-specific semantics!
|
||||
- target is a file: valid for languages like Python, where files are modules
|
||||
- target is a directory: valid for languages like Java, where directories are packages and can contain classes
|
||||
- File or directory:
|
||||
* relative_path -> new parent directory (target_relative_path)
|
||||
|
||||
:param relative_path: the relative path to the file containing the symbol to move.
|
||||
:param name_path: the name path of the symbol to move (empty for moving file or dir).
|
||||
:param target_relative_path: the relative path of the target directory or file.
|
||||
:param target_parent_name_path: the name path of the target parent symbol.
|
||||
"""
|
||||
name_path = name_path or None
|
||||
target_relative_path = target_relative_path or None
|
||||
target_parent_name_path = target_parent_name_path or None
|
||||
relative_path = self._sanitize_input_param(relative_path)
|
||||
with JetBrainsPluginClient.from_project(self.project) as client:
|
||||
response_dict = client.move(
|
||||
name_path=name_path,
|
||||
relative_path=relative_path,
|
||||
target_parent_name_path=target_parent_name_path,
|
||||
target_relative_path=target_relative_path,
|
||||
)
|
||||
return self._to_json(response_dict)
|
||||
|
||||
|
||||
class JetBrainsSafeDeleteTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional, ToolMarkerBeta):
|
||||
"""
|
||||
Safely deletes a symbol using the JetBrains backend, checking for remaining usages first
|
||||
"""
|
||||
|
||||
def apply(
|
||||
self,
|
||||
relative_path: str,
|
||||
name_path: str | None = None,
|
||||
delete_even_if_used: bool = False,
|
||||
propagate: bool = False,
|
||||
) -> str:
|
||||
"""
|
||||
Safely deletes a symbol, file, or directory, checking for usages first and propagating deletion, if desired.
|
||||
Propagation means it is possible to request deleting of usages and cleaning up of unused code.
|
||||
Propagation is powerful for cleaning up code but should be used with care, and only when you are sure that
|
||||
**Important**: this tool should always be preferred to naive deleting (e.g. via file system operations or edits).
|
||||
When using it, you don't have to search for usages first, as the tool will do it for you.
|
||||
|
||||
:param relative_path: the relative path to the file containing the symbol to delete.
|
||||
:param name_path: the name path of the symbol to delete.
|
||||
A name path identifies a symbol within a source file, e.g. "MyClass/my_method".
|
||||
Omit for deleting a file or directory.
|
||||
:param delete_even_if_used: whether to force deletion even if the symbol still has usages.
|
||||
Default is False (safe mode: will report usages instead of deleting).
|
||||
:param propagate: whether to propagate the deletion to usages of the symbol and also
|
||||
remove symbols that become unused after the deletion. Default is False.
|
||||
"""
|
||||
relative_path = self._sanitize_input_param(relative_path)
|
||||
name_path = name_path or None
|
||||
with JetBrainsPluginClient.from_project(self.project) as client:
|
||||
response_dict = client.safe_delete(
|
||||
name_path=name_path,
|
||||
relative_path=relative_path,
|
||||
delete_even_if_used=delete_even_if_used,
|
||||
propagate=propagate,
|
||||
)
|
||||
return self._to_json(response_dict)
|
||||
|
||||
|
||||
class JetBrainsInlineSymbol(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional, ToolMarkerBeta):
|
||||
"""
|
||||
Inlines a symbol using the JetBrains backend, replacing all call sites with the symbol's body
|
||||
"""
|
||||
|
||||
def apply(
|
||||
self,
|
||||
name_path: str,
|
||||
relative_path: str,
|
||||
keep_definition: bool = False,
|
||||
) -> str:
|
||||
"""
|
||||
Inlines a symbol (usually a method/function, but also classes may be amenable to inlining,
|
||||
which turns invocation into anonymous class creation),
|
||||
replacing all call sites with the symbol's body.
|
||||
**Important**: this tool should always be preferred to naive inlining (e.g. via searching for references and
|
||||
editing them).
|
||||
|
||||
:param name_path: the name path of the symbol to inline.
|
||||
:param relative_path: the relative path to the file containing the symbol to inline.
|
||||
:param keep_definition: whether to keep the original method definition after inlining all call sites.
|
||||
May be ignored in some cases (e.g. when inlining a class).
|
||||
"""
|
||||
relative_path = self._sanitize_input_param(relative_path)
|
||||
with JetBrainsPluginClient.from_project(self.project) as client:
|
||||
response_dict = client.inline_symbol(
|
||||
name_path=name_path,
|
||||
relative_path=relative_path,
|
||||
keep_definition=keep_definition,
|
||||
)
|
||||
return self._to_json(response_dict)
|
||||
|
||||
|
||||
class JetBrainsFindReferencingSymbolsTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional):
|
||||
"""
|
||||
Finds symbols that reference the given symbol using the JetBrains backend
|
||||
@@ -126,15 +261,14 @@ class JetBrainsFindReferencingSymbolsTool(Tool, ToolMarkerSymbolicRead, ToolMark
|
||||
max_answer_chars: int = -1,
|
||||
) -> str:
|
||||
"""
|
||||
Finds symbols that reference the symbol at the given `name_path`.
|
||||
Finds symbols that reference the symbol at the specified symbol, i.e. returns symbols whose definitions (e.g. a function body) contain a reference to the given symbol.
|
||||
The result will contain metadata about the referencing symbols.
|
||||
|
||||
:param name_path: name path of the symbol for which to find references; matching logic as described in find symbol tool.
|
||||
:param relative_path: the relative path to the file containing the symbol for which to find references.
|
||||
Note that here you can't pass a directory but must pass a file.
|
||||
:param name_path: name path of the symbol for which to find references
|
||||
:param relative_path: the relative path to the file containing the symbol (must be a file, not a directory)
|
||||
:param max_answer_chars: max characters for the result (-1 for default). If exceeded, no content/a shortened result is returned.
|
||||
:return: a list of JSON objects with the symbols referencing the requested symbol
|
||||
"""
|
||||
relative_path = self._sanitize_input_param(relative_path)
|
||||
with JetBrainsPluginClient.from_project(self.project) as client:
|
||||
response_dict = client.find_references(
|
||||
name_path=name_path,
|
||||
@@ -143,7 +277,19 @@ class JetBrainsFindReferencingSymbolsTool(Tool, ToolMarkerSymbolicRead, ToolMark
|
||||
)
|
||||
symbol_dicts = response_dict["symbols"]
|
||||
|
||||
# capture file paths before grouping, which mutates the dicts
|
||||
# replace reference line number (if present) by actual line/context
|
||||
for symbol_dict in symbol_dicts:
|
||||
if "reference_line_no" in symbol_dict:
|
||||
ref_line = symbol_dict["reference_line_no"]
|
||||
ref_relative_path = symbol_dict["relative_path"]
|
||||
if not SymbolDTOUtil.is_external_symbol(symbol_dict) and ref_line is not None and ref_line >= 0:
|
||||
content_around_ref = self.project.retrieve_content_around_line(
|
||||
relative_file_path=ref_relative_path, line=ref_line, context_lines_before=1, context_lines_after=1
|
||||
)
|
||||
symbol_dict["context"] = content_around_ref.to_display_string()
|
||||
del symbol_dict["reference_line_no"]
|
||||
|
||||
# capture file paths before grouping
|
||||
ref_paths = [s.get("relative_path", "unknown") for s in symbol_dicts]
|
||||
|
||||
result = self.symbol_dict_grouper.group(symbol_dicts)
|
||||
@@ -188,8 +334,8 @@ class JetBrainsGetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOp
|
||||
:param depth: depth up to which descendants shall be retrieved (e.g., use 1 to also retrieve immediate children).
|
||||
:param max_answer_chars: max characters for the result (-1 for default). If exceeded, no content/a shortened result is returned.
|
||||
:param include_file_documentation: whether to include the file's docstring. Default False.
|
||||
:return: a JSON object containing the symbols grouped by kind in a compact format.
|
||||
"""
|
||||
relative_path = self._sanitize_input_param(relative_path)
|
||||
with JetBrainsPluginClient.from_project(self.project) as client:
|
||||
symbol_overview = client.get_symbols_overview(
|
||||
relative_path=relative_path, depth=depth, include_file_documentation=include_file_documentation
|
||||
@@ -292,6 +438,7 @@ class JetBrainsTypeHierarchyTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptiona
|
||||
-1 means the default value from the config will be used.
|
||||
:return: Compact JSON with file-grouped hierarchy. Error string if not applicable.
|
||||
"""
|
||||
relative_path = self._sanitize_input_param(relative_path)
|
||||
with JetBrainsPluginClient.from_project(self.project) as client:
|
||||
subtypes = None
|
||||
supertypes = None
|
||||
@@ -327,3 +474,94 @@ class JetBrainsTypeHierarchyTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptiona
|
||||
|
||||
result = self._to_json(result_dict)
|
||||
return self._limit_length(result, max_answer_chars)
|
||||
|
||||
|
||||
class JetBrainsFindDeclarationTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional):
|
||||
"""
|
||||
Finds the declaration of a symbol using the JetBrains backend
|
||||
"""
|
||||
|
||||
def apply(self, relative_path: str, regex: str, include_body: bool = False) -> str:
|
||||
r"""
|
||||
Finds the declaration of a symbol.
|
||||
|
||||
:param relative_path: the relative path to the source file containing the symbol for which to find the declaration.
|
||||
:param regex: a regular expression with one group, where the group matches the symbol for which to perform the lookup.
|
||||
For example, to find the declaration of the `process` method in a call like `obj.process()`,
|
||||
pass an expression like "obj\.(process)\(process_input_arg=37\)".
|
||||
Prefer regexes with sufficiently large context around the group to render the match unambiguous.
|
||||
Uses Python syntax with MULTILINE and DOTALL flags enabled.
|
||||
:param include_body: whether to include the symbol's body in the result. Default False.
|
||||
"""
|
||||
relative_path = self._sanitize_input_param(relative_path)
|
||||
regex = self._sanitize_input_param(regex)
|
||||
|
||||
editor = self.create_code_editor()
|
||||
content = editor.read_file(relative_path)
|
||||
coords = find_text_coordinates(content, regex, require_unique=True)
|
||||
assert coords is not None
|
||||
with JetBrainsPluginClient.from_project(self.project) as client:
|
||||
symbol_collection = client.find_declaration(
|
||||
relative_path=relative_path, line=coords.line, col=coords.col, include_quick_info=False, include_body=include_body
|
||||
)
|
||||
result = self._to_json(symbol_collection)
|
||||
return result
|
||||
|
||||
|
||||
class JetBrainsFindImplementationsTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional):
|
||||
"""
|
||||
Finds the implementations of a symbol using the JetBrains backend
|
||||
"""
|
||||
|
||||
def apply(self, relative_path: str, name_path: str) -> str:
|
||||
"""
|
||||
Finds the implementations of a symbol.
|
||||
|
||||
:param relative_path: the relative path to the source file containing the symbol for which to find implementations.
|
||||
:param name_path: name path of the symbol for which to find implementations
|
||||
"""
|
||||
with JetBrainsPluginClient.from_project(self.project) as client:
|
||||
symbol_collection = client.find_implementations(
|
||||
relative_path=relative_path,
|
||||
name_path=name_path,
|
||||
include_quick_info=False,
|
||||
)
|
||||
result = self._to_json(symbol_collection)
|
||||
return result
|
||||
|
||||
|
||||
class JetBrainsRenameTool(Tool, ToolMarkerSymbolicEdit, ToolMarkerOptional):
|
||||
"""
|
||||
Renames a symbol, file or directory throughout the codebase using the JetBrains backend.
|
||||
"""
|
||||
|
||||
def apply(
|
||||
self,
|
||||
relative_path: str,
|
||||
new_name: str,
|
||||
name_path: str | None = None,
|
||||
rename_in_comments: bool = False,
|
||||
rename_in_text_occurrences: bool = False,
|
||||
) -> str:
|
||||
"""
|
||||
Renames a symbol, file or directory throughout the codebase.
|
||||
Note: renaming in comments/text is on a best-effort basis by the IDE; if the symbol name is non-unique, further
|
||||
verification is recommended.
|
||||
|
||||
:param relative_path: if `name_path` is passed, the relative path of the file containing the symbol.
|
||||
Otherwise, the path to the directory or file to rename.
|
||||
:param new_name: the new name
|
||||
:param name_path: the name path of the symbol to rename or None if renaming a file or directory.
|
||||
:param rename_in_comments: whether to also rename occurrences in comments. Default True.
|
||||
:param rename_in_text_occurrences: whether to also rename occurrences in text. Default True.
|
||||
:return: a status message
|
||||
"""
|
||||
code_editor = JetBrainsCodeEditor(self.project)
|
||||
result = code_editor.rename_symbol(
|
||||
name_path=name_path,
|
||||
relative_path=relative_path,
|
||||
new_name=new_name,
|
||||
rename_in_comments=rename_in_comments,
|
||||
rename_in_text_occurrences=rename_in_text_occurrences,
|
||||
)
|
||||
return self._to_json(result)
|
||||
@@ -258,7 +258,8 @@ class FindReferencingSymbolsTool(Tool, ToolMarkerSymbolicRead):
|
||||
|
||||
:param name_path: for finding the symbol to find references for, same logic as in the `find_symbol` tool.
|
||||
:param relative_path: the relative path to the file containing the symbol for which to find references.
|
||||
Note that here you can't pass a directory but must pass a file.
|
||||
Note: for external dependencies, this must be an identifier starting with `<ext` that you have received
|
||||
earlier (don't try to guess!).
|
||||
:param include_kinds: same as in the `find_symbol` tool.
|
||||
:param exclude_kinds: same as in the `find_symbol` tool.
|
||||
:param max_answer_chars: same as in the `find_symbol` tool.
|
||||
@@ -290,7 +291,7 @@ class FindReferencingSymbolsTool(Tool, ToolMarkerSymbolicRead):
|
||||
ref_dict["content_around_reference"] = content_around_ref.to_display_string()
|
||||
reference_dicts.append(ref_dict)
|
||||
|
||||
# capture lightweight reference data before grouping, which mutates the dicts
|
||||
# capture lightweight reference data before grouping
|
||||
ref_summaries = []
|
||||
for ref, d in zip(references_in_symbols, reference_dicts, strict=True):
|
||||
ref_summaries.append(
|
||||
@@ -409,6 +410,7 @@ class InsertBeforeSymbolTool(Tool, ToolMarkerSymbolicEdit):
|
||||
class RenameSymbolTool(Tool, ToolMarkerSymbolicEdit):
|
||||
"""
|
||||
Renames a symbol throughout the codebase using language server refactoring capabilities.
|
||||
For JB, we use a separate tool.
|
||||
"""
|
||||
|
||||
def apply(
|
||||
@@ -427,6 +429,47 @@ class RenameSymbolTool(Tool, ToolMarkerSymbolicEdit):
|
||||
:param new_name: the new name for the symbol
|
||||
:return: result summary indicating success or failure
|
||||
"""
|
||||
code_editor = self.create_code_editor()
|
||||
status_message = code_editor.rename_symbol(name_path, relative_file_path=relative_path, new_name=new_name)
|
||||
code_editor = self.create_ls_code_editor()
|
||||
status_message = code_editor.rename_symbol(name_path, relative_path=relative_path, new_name=new_name)
|
||||
return status_message
|
||||
|
||||
|
||||
class SafeDeleteSymbol(Tool, ToolMarkerSymbolicEdit):
|
||||
def apply(
|
||||
self,
|
||||
name_path_pattern: str,
|
||||
relative_path: str,
|
||||
) -> str:
|
||||
"""
|
||||
Deletes the symbol if it is safe to do so (i.e., if there are no references to it)
|
||||
or returns a list of references to it.
|
||||
|
||||
:param name_path_pattern: name path of the symbol to delete (definitions in the `find_symbol` tool apply)
|
||||
:param relative_path: the relative path to the file containing the symbol to delete
|
||||
"""
|
||||
ls_symbol_retriever = self.create_language_server_symbol_retriever()
|
||||
symbol = ls_symbol_retriever.find_unique(name_path_pattern, substring_matching=False, within_relative_path=relative_path)
|
||||
symbol_rel_path = symbol.relative_path
|
||||
assert symbol_rel_path is not None, f"Symbol {name_path_pattern} has no relative path, this is likely a bug."
|
||||
assert symbol_rel_path == relative_path, f"Symbol {name_path_pattern} is not in the expected relative path {relative_path}."
|
||||
symbol_name_path = symbol.get_name_path()
|
||||
|
||||
symbol_line = symbol.line
|
||||
symbol_col = symbol.column
|
||||
assert symbol_line is not None and symbol_col is not None, (
|
||||
f"Symbol {name_path_pattern} has no identifier position, this is likely a bug."
|
||||
)
|
||||
lang_server = ls_symbol_retriever.get_language_server(symbol_rel_path)
|
||||
references_locations = lang_server.request_references(symbol_rel_path, symbol_line, symbol_col)
|
||||
file_to_lines: dict[str, list[int]] = defaultdict(list)
|
||||
if references_locations:
|
||||
for ref_loc in references_locations:
|
||||
ref_relative_path = ref_loc.get("relativePath")
|
||||
if ref_relative_path is None:
|
||||
continue
|
||||
file_to_lines[ref_relative_path].append(ref_loc["range"]["start"]["line"])
|
||||
if file_to_lines:
|
||||
return f"Cannot delete, the symbol {symbol_name_path} is referenced in: {self._to_json(file_to_lines)}"
|
||||
code_editor = self.create_ls_code_editor()
|
||||
code_editor.delete_symbol(symbol_name_path, relative_file_path=symbol_rel_path)
|
||||
return SUCCESS_RESULT
|
||||
@@ -21,7 +21,7 @@ from solidlsp.ls_exceptions import SolidLSPException
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from serena.agent import SerenaAgent
|
||||
from serena.code_editor import CodeEditor
|
||||
from serena.code_editor import CodeEditor, LanguageServerCodeEditor
|
||||
from serena.symbol import LanguageServerSymbolRetriever
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -58,16 +58,23 @@ class Component(ABC):
|
||||
return self.agent.get_active_project_or_raise()
|
||||
|
||||
def create_code_editor(self) -> "CodeEditor":
|
||||
from ..code_editor import JetBrainsCodeEditor, LanguageServerCodeEditor
|
||||
from ..code_editor import JetBrainsCodeEditor
|
||||
|
||||
match self.agent.get_language_backend():
|
||||
case LanguageBackend.LSP:
|
||||
return LanguageServerCodeEditor(self.create_language_server_symbol_retriever())
|
||||
return self.create_ls_code_editor()
|
||||
case LanguageBackend.JETBRAINS:
|
||||
return JetBrainsCodeEditor(project=self.project)
|
||||
case _:
|
||||
raise ValueError
|
||||
|
||||
def create_ls_code_editor(self) -> "LanguageServerCodeEditor":
|
||||
from ..code_editor import LanguageServerCodeEditor
|
||||
|
||||
if not self.agent.is_using_language_server():
|
||||
raise Exception("Cannot create LanguageServerCodeEditor; agent is not in language server mode.")
|
||||
return LanguageServerCodeEditor(self.create_language_server_symbol_retriever())
|
||||
|
||||
|
||||
class ToolMarker:
|
||||
"""
|
||||
@@ -103,6 +110,12 @@ class ToolMarkerSymbolicEdit(ToolMarkerCanEdit):
|
||||
"""
|
||||
|
||||
|
||||
class ToolMarkerBeta(ToolMarker):
|
||||
"""
|
||||
Marker for tools that are considered beta features (may not be fully robust)
|
||||
"""
|
||||
|
||||
|
||||
class ApplyMethodProtocol(Protocol):
|
||||
"""Callable protocol for the apply method of a tool."""
|
||||
|
||||
@@ -124,6 +137,11 @@ class Tool(Component):
|
||||
_last_tool_call_client_str: str | None = None
|
||||
"""We can only get the client info from within a tool call. Each tool call will update this variable."""
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_input_param(raw_param: str) -> str:
|
||||
# some clients replace < and > with their escaped html versions, we need to counteract this
|
||||
return raw_param.replace("<", "<").replace(">", ">")
|
||||
|
||||
@classmethod
|
||||
def set_last_tool_call_client_str(cls, client_str: str | None) -> None:
|
||||
cls._last_tool_call_client_str = client_str
|
||||
@@ -400,6 +418,7 @@ class EditedFileContext:
|
||||
class RegisteredTool:
|
||||
tool_class: type[Tool]
|
||||
is_optional: bool
|
||||
is_beta: bool
|
||||
tool_name: str
|
||||
|
||||
@property
|
||||
@@ -415,6 +434,14 @@ tool_packages = ["serena.tools"]
|
||||
|
||||
@singleton
|
||||
class ToolRegistry:
|
||||
_deleted_tools: list[str] = [
|
||||
"think_about_collected_information",
|
||||
"prepare_for_new_conversation",
|
||||
"summarize_changes",
|
||||
"think_about_whether_you_are_done",
|
||||
"switch_modes",
|
||||
]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._tool_dict: dict[str, RegisteredTool] = {}
|
||||
inclusion_predicate = lambda c: "apply" in c.__dict__ # include only concrete tool classes that implement apply
|
||||
@@ -422,10 +449,11 @@ class ToolRegistry:
|
||||
if not any(cls.__module__.startswith(pkg) for pkg in tool_packages):
|
||||
continue
|
||||
is_optional = issubclass(cls, ToolMarkerOptional)
|
||||
is_beta = issubclass(cls, ToolMarkerBeta)
|
||||
name = cls.get_name_from_cls()
|
||||
if name in self._tool_dict:
|
||||
raise ValueError(f"Duplicate tool name found: {name}. Tool classes must have unique names.")
|
||||
self._tool_dict[name] = RegisteredTool(tool_class=cls, is_optional=is_optional, tool_name=name)
|
||||
self._tool_dict[name] = RegisteredTool(tool_class=cls, is_optional=is_optional, tool_name=name, is_beta=is_beta)
|
||||
|
||||
def get_registered_tools_by_module(self) -> dict[str, list[RegisteredTool]]:
|
||||
"""
|
||||
@@ -503,3 +531,15 @@ class ToolRegistry:
|
||||
|
||||
def is_valid_tool_name(self, tool_name: str) -> bool:
|
||||
return tool_name in self._tool_dict
|
||||
|
||||
def check_valid_tool_name(self, tool_name: str, caller_context_for_logging: str = "") -> bool:
|
||||
"""Returns True if the tool name is valid, False if it is deleted, and raises ValueError if it is invalid."""
|
||||
if self.is_deleted_tool_name(tool_name):
|
||||
log.warning(f"Tool name is deleted: {tool_name}{caller_context_for_logging}")
|
||||
return False
|
||||
if not self.is_valid_tool_name(tool_name):
|
||||
raise ValueError(f"Invalid tool name: {tool_name}{caller_context_for_logging}")
|
||||
return True
|
||||
|
||||
def is_deleted_tool_name(self, tool_name: str) -> bool:
|
||||
return tool_name in self._deleted_tools
|
||||
@@ -4,7 +4,7 @@ Tools supporting the general workflow of the agent
|
||||
|
||||
import platform
|
||||
|
||||
from serena.tools import Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional
|
||||
from serena.tools import ReadMemoryTool, Tool, ToolMarkerDoesNotRequireActiveProject, WriteMemoryTool
|
||||
|
||||
|
||||
class CheckOnboardingPerformedTool(Tool):
|
||||
@@ -17,12 +17,16 @@ class CheckOnboardingPerformedTool(Tool):
|
||||
Checks whether project onboarding was already performed.
|
||||
You should always call this tool before beginning to actually work on the project/after activating a project.
|
||||
"""
|
||||
read_memory_tool_available = self.agent.tool_is_exposed(ReadMemoryTool.get_name_from_cls())
|
||||
perform_onboarding_tool_available = self.agent.tool_is_exposed(OnboardingTool.get_name_from_cls())
|
||||
|
||||
if not read_memory_tool_available:
|
||||
return "Memory reading tool not activated, skipping onboarding check."
|
||||
project_memories = self.memories_manager.list_project_memories()
|
||||
if len(project_memories) == 0:
|
||||
msg = (
|
||||
"Onboarding not performed yet (no memories available). "
|
||||
"You should perform onboarding by calling the `onboarding` tool before proceeding with the task. "
|
||||
)
|
||||
msg = "Onboarding not performed yet (no memories available). "
|
||||
if perform_onboarding_tool_available:
|
||||
msg += "You should perform onboarding by calling the `onboarding` tool before proceeding with the task. "
|
||||
else:
|
||||
# Not reporting the list of memories here, as they were already reported at project activation
|
||||
# (with the system prompt if the project was activated at startup)
|
||||
@@ -46,84 +50,17 @@ class OnboardingTool(Tool):
|
||||
|
||||
:return: instructions on how to create the onboarding information
|
||||
"""
|
||||
write_memory_tool_available = self.agent.tool_is_exposed(WriteMemoryTool.get_name_from_cls())
|
||||
if not write_memory_tool_available:
|
||||
return "Memory writing tool not activated, skipping onboarding."
|
||||
system = platform.system()
|
||||
return self.prompt_factory.create_onboarding_prompt(system=system)
|
||||
|
||||
|
||||
class ThinkAboutCollectedInformationTool(Tool, ToolMarkerOptional):
|
||||
"""
|
||||
Thinking tool for pondering the completeness of collected information.
|
||||
"""
|
||||
|
||||
def apply(self) -> str:
|
||||
"""
|
||||
Think about the collected information and whether it is sufficient and relevant.
|
||||
This tool should ALWAYS be called after you have completed a non-trivial sequence of searching steps like
|
||||
find_symbol, find_referencing_symbols, search_files_for_pattern, read_file, etc.
|
||||
"""
|
||||
return self.prompt_factory.create_think_about_collected_information()
|
||||
|
||||
|
||||
class ThinkAboutTaskAdherenceTool(Tool, ToolMarkerOptional):
|
||||
"""
|
||||
Thinking tool for determining whether the agent is still on track with the current task.
|
||||
"""
|
||||
|
||||
def apply(self) -> str:
|
||||
"""
|
||||
Think about the task at hand and whether you are still on track.
|
||||
Especially important if the conversation has been going on for a while and there
|
||||
has been a lot of back and forth.
|
||||
|
||||
This tool should ALWAYS be called before you insert, replace, or delete code.
|
||||
"""
|
||||
return self.prompt_factory.create_think_about_task_adherence()
|
||||
|
||||
|
||||
class ThinkAboutWhetherYouAreDoneTool(Tool, ToolMarkerOptional):
|
||||
"""
|
||||
Thinking tool for determining whether the task is truly completed.
|
||||
"""
|
||||
|
||||
def apply(self) -> str:
|
||||
"""
|
||||
Whenever you feel that you are done with what the user has asked for, it is important to call this tool.
|
||||
"""
|
||||
return self.prompt_factory.create_think_about_whether_you_are_done()
|
||||
|
||||
|
||||
class SummarizeChangesTool(Tool, ToolMarkerOptional):
|
||||
"""
|
||||
Provides instructions for summarizing the changes made to the codebase.
|
||||
"""
|
||||
|
||||
def apply(self) -> str:
|
||||
"""
|
||||
Summarize the changes you have made to the codebase.
|
||||
This tool should always be called after you have fully completed any non-trivial coding task,
|
||||
but only after the think_about_whether_you_are_done call.
|
||||
"""
|
||||
return self.prompt_factory.create_summarize_changes()
|
||||
|
||||
|
||||
class PrepareForNewConversationTool(Tool):
|
||||
"""
|
||||
Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
|
||||
"""
|
||||
|
||||
def apply(self) -> str:
|
||||
"""
|
||||
Instructions for preparing for a new conversation. This tool should only be called on explicit user request.
|
||||
"""
|
||||
return self.prompt_factory.create_prepare_for_new_conversation()
|
||||
|
||||
|
||||
class InitialInstructionsTool(Tool, ToolMarkerDoesNotRequireActiveProject):
|
||||
"""
|
||||
Provides instructions on how to use the Serena toolbox.
|
||||
Should only be used in settings where the system prompt is not read automatically by the client.
|
||||
|
||||
NOTE: Some MCP clients (including Claude Desktop) do not read the system prompt automatically!
|
||||
Provides instructions Serena usage (i.e. the 'Serena Instructions Manual')
|
||||
for clients that do not read the initial instructions when the MCP server is connected.
|
||||
"""
|
||||
|
||||
def apply(self) -> str:
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import click
|
||||
|
||||
|
||||
def ask_yes_no(question: str, default: bool | None = None) -> bool:
|
||||
default_prompt = "Y/n" if default else "y/N"
|
||||
|
||||
@@ -10,3 +13,24 @@ def ask_yes_no(question: str, default: bool | None = None) -> bool:
|
||||
if answer in ("n", "no"):
|
||||
return False
|
||||
print("Please answer yes/y or no/n.")
|
||||
|
||||
|
||||
class AutoRegisteringGroup(click.Group):
|
||||
"""
|
||||
A click.Group subclass that automatically registers any click.Command
|
||||
attributes defined on the class into the group.
|
||||
|
||||
After initialization, it inspects its own class for attributes that are
|
||||
instances of click.Command (typically created via @click.command) and
|
||||
calls self.add_command(cmd) on each. This lets you define your commands
|
||||
as static methods on the subclass for IDE-friendly organization without
|
||||
manual registration.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, help: str):
|
||||
super().__init__(name=name, help=help)
|
||||
# Scan class attributes for click.Command instances and register them.
|
||||
for attr in dir(self.__class__):
|
||||
cmd = getattr(self.__class__, attr)
|
||||
if isinstance(cmd, click.Command):
|
||||
self.add_command(cmd)
|
||||
@@ -165,15 +165,23 @@ class GitignoreParser:
|
||||
|
||||
def scan(abs_path: str | None) -> Iterator[str]:
|
||||
for entry in os.scandir(abs_path):
|
||||
if entry.is_dir(follow_symlinks=follow_symlinks):
|
||||
queue.append(entry.path)
|
||||
elif entry.is_file(follow_symlinks=follow_symlinks) and entry.name == ".gitignore":
|
||||
yield entry.path
|
||||
try:
|
||||
if entry.is_dir(follow_symlinks=follow_symlinks):
|
||||
queue.append(entry.path)
|
||||
elif entry.is_file(follow_symlinks=follow_symlinks) and entry.name == ".gitignore":
|
||||
yield entry.path
|
||||
except PermissionError as ex:
|
||||
log.debug(f"Skipping entry due to permission error: {entry.path}", exc_info=ex)
|
||||
continue
|
||||
|
||||
while queue:
|
||||
next_abs_path = queue.pop(0)
|
||||
if next_abs_path != self.repo_root:
|
||||
rel_path = os.path.relpath(next_abs_path, self.repo_root)
|
||||
try:
|
||||
rel_path = os.path.relpath(next_abs_path, self.repo_root)
|
||||
except ValueError:
|
||||
# If the path is on a different drive (Windows) or cannot be made relative for another reason, we ignore it
|
||||
continue
|
||||
if self.should_ignore(rel_path):
|
||||
continue
|
||||
yield from scan(next_abs_path)
|
||||
@@ -337,6 +345,9 @@ def match_path(relative_path: str, path_spec: PathSpec, root_path: str = "") ->
|
||||
:param root_path: the root path from which the relative path is derived
|
||||
:return:
|
||||
"""
|
||||
if str(relative_path) in {"", "."}:
|
||||
return False
|
||||
|
||||
normalized_path = str(relative_path).replace(os.path.sep, "/")
|
||||
|
||||
# We can have patterns like /src/..., which would only match corresponding paths from the repo root
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
|
||||
from sensai.util.git import GitStatus
|
||||
|
||||
from ..constants import REPO_ROOT
|
||||
from .shell import subprocess_check_output
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -9,10 +10,11 @@ log = logging.getLogger(__name__)
|
||||
|
||||
def get_git_status() -> GitStatus | None:
|
||||
try:
|
||||
commit_hash = subprocess_check_output(["git", "rev-parse", "HEAD"])
|
||||
unstaged = bool(subprocess_check_output(["git", "diff", "--name-only"]))
|
||||
staged = bool(subprocess_check_output(["git", "diff", "--staged", "--name-only"]))
|
||||
untracked = bool(subprocess_check_output(["git", "ls-files", "--others", "--exclude-standard"]))
|
||||
cwd = REPO_ROOT
|
||||
commit_hash = subprocess_check_output(["git", "rev-parse", "HEAD"], cwd=cwd)
|
||||
unstaged = bool(subprocess_check_output(["git", "diff", "--name-only"], cwd=cwd))
|
||||
staged = bool(subprocess_check_output(["git", "diff", "--staged", "--name-only"], cwd=cwd))
|
||||
untracked = bool(subprocess_check_output(["git", "ls-files", "--others", "--exclude-standard"], cwd=cwd))
|
||||
return GitStatus(
|
||||
commit=commit_hash, has_unstaged_changes=unstaged, has_staged_uncommitted_changes=staged, has_untracked_files=untracked
|
||||
)
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import psutil
|
||||
import webview
|
||||
from PIL import Image
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WebViewWithTray:
|
||||
"""
|
||||
Web view window with optional system tray.
|
||||
"""
|
||||
|
||||
DEBUG = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
url: str,
|
||||
*,
|
||||
title: str,
|
||||
start_minimized: bool = False,
|
||||
width: int = 1400,
|
||||
height: int = 900,
|
||||
tray: bool = False,
|
||||
parent_process_id: int | None = None,
|
||||
app_id: str | None = None,
|
||||
app_icon_path: str | None = None,
|
||||
tray_icon_path: str | None = None,
|
||||
):
|
||||
"""
|
||||
:param url: the URL to be show in the web view
|
||||
:param title: the title of the window
|
||||
:param start_minimized: whether to show the window minimised on startup (if `tray` is enabled, minimised to tray)
|
||||
:param width: width of the window in pixels
|
||||
:param height: height of the window in pixels
|
||||
:param tray: whether to enable the system tray icon and functionality.
|
||||
If this is enabled, closing the window will minimise it to tray instead of quitting the application.
|
||||
:param parent_process_id: the PID of the parent process to monitor; if specified, the viewer will automatically
|
||||
shut itself down when the parent process exits.
|
||||
:param app_id: the application ID to use for the viewer to ensure that the window is grouped separately (with separate
|
||||
icon) in the taskbar
|
||||
"""
|
||||
self._url = url
|
||||
self._use_tray = tray
|
||||
self._title = title
|
||||
self._start_minimized = start_minimized
|
||||
self._parent_process_id = parent_process_id
|
||||
self._app_id = app_id
|
||||
|
||||
self.window: webview.Window
|
||||
self._tray_icon: Any
|
||||
self._quitting = False
|
||||
self._app_icon_path = app_icon_path
|
||||
self._tray_icon_path = tray_icon_path
|
||||
|
||||
# Create hidden to avoid flash; show/restore/minimize in start callback.
|
||||
window = webview.create_window(
|
||||
self._title,
|
||||
self._url,
|
||||
width=width,
|
||||
height=height,
|
||||
hidden=self._start_minimized,
|
||||
text_select=True,
|
||||
zoomable=True,
|
||||
)
|
||||
assert window is not None
|
||||
self.window = window
|
||||
|
||||
def run(self) -> None:
|
||||
# set app id (avoid app being lumped together with other Python-based apps in Windows taskbar)
|
||||
if self._app_id is not None and sys.platform == "win32":
|
||||
import ctypes
|
||||
|
||||
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("oraios.serena")
|
||||
|
||||
if self._use_tray:
|
||||
self.window.events.closing += self._on_closing
|
||||
self._start_tray()
|
||||
|
||||
def _start_callback() -> None:
|
||||
if self._start_minimized:
|
||||
self._hide_window()
|
||||
else:
|
||||
self._show_window()
|
||||
|
||||
# start a thread to monitor the parent process
|
||||
if self._parent_process_id is not None:
|
||||
threading.Thread(target=self._monitor_parent_process, daemon=True).start()
|
||||
|
||||
webview.start(_start_callback, icon=self._app_icon_path)
|
||||
|
||||
def _monitor_parent_process(self) -> None:
|
||||
"""
|
||||
Monitors the parent process and shuts down the dashboard viewer if the parent process exits.
|
||||
"""
|
||||
pid = self._parent_process_id
|
||||
log.info("Starting parent process monitor thread (parent_pid=%d, pid=%s)", pid, os.getpid())
|
||||
try:
|
||||
parent_process = psutil.Process(pid)
|
||||
while parent_process.is_running():
|
||||
time.sleep(1)
|
||||
except psutil.NoSuchProcess:
|
||||
pass # Parent process already exited
|
||||
log.info("Parent process (pid=%d) has exited, shutting down dashboard viewer", pid)
|
||||
self._terminate()
|
||||
|
||||
def _show_window(self) -> None:
|
||||
if not self.window:
|
||||
return
|
||||
|
||||
if sys.platform == "darwin":
|
||||
from PyObjCTools.AppHelper import callAfter
|
||||
|
||||
callAfter(self._show_window_on_macos)
|
||||
else:
|
||||
self.window.show()
|
||||
self.window.restore()
|
||||
|
||||
def _hide_window(self) -> None:
|
||||
if not self.window:
|
||||
return
|
||||
|
||||
if sys.platform == "darwin":
|
||||
from PyObjCTools.AppHelper import callAfter
|
||||
|
||||
callAfter(self._hide_window_on_macos)
|
||||
else:
|
||||
self.window.hide()
|
||||
|
||||
def _show_window_on_macos(self) -> None:
|
||||
from AppKit import (
|
||||
NSApplication,
|
||||
NSApplicationActivationPolicyRegular,
|
||||
)
|
||||
from PyObjCTools.AppHelper import callLater
|
||||
|
||||
ns_app = NSApplication.sharedApplication()
|
||||
ns_app.setActivationPolicy_(NSApplicationActivationPolicyRegular)
|
||||
self._set_macos_app_icon(ns_app)
|
||||
ns_app.unhide_(None)
|
||||
ns_app.activateIgnoringOtherApps_(True)
|
||||
# Give the status item menu a beat to close before restoring the window.
|
||||
callLater(0.1, self._restore_window_on_macos)
|
||||
|
||||
def _restore_window_on_macos(self) -> None:
|
||||
self.window.show()
|
||||
self.window.restore()
|
||||
|
||||
def _hide_window_on_macos(self) -> None:
|
||||
from AppKit import (
|
||||
NSApplication,
|
||||
NSApplicationActivationPolicyAccessory,
|
||||
)
|
||||
|
||||
self.window.hide()
|
||||
NSApplication.sharedApplication().setActivationPolicy_(NSApplicationActivationPolicyAccessory)
|
||||
|
||||
def _set_macos_app_icon(self, ns_app: Any) -> None:
|
||||
if not self._app_icon_path:
|
||||
return
|
||||
|
||||
from AppKit import NSImage
|
||||
|
||||
ns_image = NSImage.alloc().initByReferencingFile_(self._app_icon_path)
|
||||
if ns_image is not None:
|
||||
ns_app.setApplicationIconImage_(ns_image)
|
||||
|
||||
def _on_closing(self) -> bool:
|
||||
"""Intercept window close: hide window instead of quitting (macOS standard behavior)."""
|
||||
if self._quitting:
|
||||
return True
|
||||
self._hide_window()
|
||||
return False # prevent the window from actually closing
|
||||
|
||||
def _terminate(self) -> None:
|
||||
"""
|
||||
Terminates the viewer application and the tray icon
|
||||
"""
|
||||
self._quitting = True
|
||||
try:
|
||||
if self._tray_icon:
|
||||
self._tray_icon.stop()
|
||||
finally:
|
||||
if self.window:
|
||||
self.window.destroy()
|
||||
|
||||
def _start_tray(self) -> None:
|
||||
# import pystray locally, because the import fails when there is no display!
|
||||
import pystray
|
||||
from pystray import MenuItem as Item
|
||||
from pystray._base import Icon as TrayIcon
|
||||
|
||||
icon_img = None
|
||||
if self._tray_icon_path is not None:
|
||||
icon_img = Image.open(self._tray_icon_path)
|
||||
|
||||
def show(_icon: TrayIcon, _item: Item) -> None:
|
||||
self._show_window()
|
||||
|
||||
def hide(_icon: TrayIcon, _item: Item) -> None:
|
||||
self._hide_window()
|
||||
|
||||
def quit_app(_icon: TrayIcon, _item: Item) -> None:
|
||||
self._terminate()
|
||||
|
||||
menu = pystray.Menu(
|
||||
Item("Open", show, default=True),
|
||||
Item("Hide", hide),
|
||||
Item("Quit", quit_app),
|
||||
)
|
||||
|
||||
kwargs: dict[str, Any] = {}
|
||||
if sys.platform == "darwin":
|
||||
# Passing darwin_nsapplication integrates pystray with the NSApplication
|
||||
# run loop that webview.start() is about to enter. sharedApplication()
|
||||
# is idempotent; pywebview will reuse the same singleton.
|
||||
from AppKit import NSApplication
|
||||
|
||||
kwargs["darwin_nsapplication"] = NSApplication.sharedApplication()
|
||||
|
||||
self._tray_icon = pystray.Icon("dashboard_viewer", icon_img, self._title, menu, **kwargs)
|
||||
|
||||
# On Windows/Linux, run_detached spawns pystray's own internal thread and
|
||||
# returns immediately. On macOS it hooks into the NSApplication run loop
|
||||
# that webview.start() is about to enter (run_detached is always called
|
||||
# before webview.start() on macOS — see run()).
|
||||
self._tray_icon.run_detached()
|
||||
@@ -42,9 +42,11 @@ def execute_shell_command(command: str, cwd: str | None = None, capture_stderr:
|
||||
return ShellCommandResult(stdout=stdout, stderr=stderr, return_code=process.returncode, cwd=cwd)
|
||||
|
||||
|
||||
def subprocess_check_output(args: list[str], encoding: str = "utf-8", strip: bool = True, timeout: float | None = None) -> str:
|
||||
def subprocess_check_output(
|
||||
args: list[str], encoding: str = "utf-8", strip: bool = True, timeout: float | None = None, cwd: str | None = None
|
||||
) -> str:
|
||||
output = subprocess.check_output(
|
||||
args, stdin=subprocess.DEVNULL, stderr=subprocess.PIPE, timeout=timeout, env=os.environ.copy(), **subprocess_kwargs()
|
||||
args, stdin=subprocess.DEVNULL, stderr=subprocess.PIPE, timeout=timeout, env=os.environ.copy(), cwd=cwd, **subprocess_kwargs()
|
||||
).decode(encoding) # type: ignore
|
||||
if strip:
|
||||
output = output.strip()
|
||||
|
||||
@@ -11,6 +11,7 @@ from bs4 import BeautifulSoup
|
||||
from joblib import Parallel, delayed
|
||||
|
||||
from serena.constants import DEFAULT_SOURCE_FILE_ENCODING
|
||||
from solidlsp.ls_utils import TextUtils
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -537,21 +538,29 @@ class TextCoords:
|
||||
"""
|
||||
|
||||
|
||||
def find_text_coordinates(content: str, regex: str) -> TextCoords | None:
|
||||
def find_text_coordinates(content: str, regex: str, require_unique: bool = False) -> TextCoords | None:
|
||||
"""
|
||||
Finds the line and column number of the first match of a regex pattern in the given content.
|
||||
|
||||
:param content: the text content to search through
|
||||
:param regex: the regular expression pattern to search for; it must match part of a single line,
|
||||
and contain exactly one group that captures the position of interest (e.g., the exact variable name to find the coordinates of)
|
||||
:return: a tuple of (line_number, column_number) for the first match found, both 0-based, or None if no match is found
|
||||
:param require_unique: if True, raises an error if not exactly one match is found;
|
||||
if False, returns None if no match is found, and returns the coordinates of the first match if multiple matches are found
|
||||
:return: the coordinates of the match or None
|
||||
"""
|
||||
pattern = re.compile(regex)
|
||||
for line_number, line in enumerate(content.splitlines()):
|
||||
match = pattern.search(line)
|
||||
if match:
|
||||
if len(match.groups()) != 1:
|
||||
raise ValueError(f"Regex must contain exactly one group to capture the position, but found {len(match.groups())} groups.")
|
||||
column_number = match.start(1)
|
||||
return TextCoords(line_number, column_number)
|
||||
return None
|
||||
pattern = re.compile(regex, flags=re.MULTILINE | re.DOTALL)
|
||||
matches = list(pattern.finditer(content))
|
||||
if len(matches) == 0:
|
||||
if require_unique:
|
||||
raise ValueError(f"No match found for regex: {regex}")
|
||||
return None
|
||||
else:
|
||||
if require_unique and len(matches) > 1:
|
||||
raise ValueError(f"Match must be unique; found {len(matches)} matches for regex: {regex}")
|
||||
match = matches[0]
|
||||
if len(match.groups()) != 1:
|
||||
raise ValueError(f"Regex must contain exactly one group to capture the position, but found {len(match.groups())} groups.")
|
||||
index_in_content = match.start(1)
|
||||
line, col = TextUtils.get_line_col_from_index(content, index_in_content)
|
||||
return TextCoords(line, col)
|
||||
Loaded 100 of 145 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user