From a6877c4419db98fd10c6b665bc2d6064adfdad68 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Tue, 21 Apr 2026 17:01:47 +0200 Subject: [PATCH] Project server: - query_project: Support use of project root instead of project name - list_queryable_projects: Return both project names and project roots Resolves #1388 --- CHANGELOG.md | 3 +++ src/serena/project_server.py | 31 ++++++++++++++----------- src/serena/tools/query_project_tools.py | 12 ++++------ 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c5fbe38..836f2a23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ Status of the `main` branch. Changes prior to the next official version change w - Fix: restricted `insert_after_symbol` to raise if used on an assignment or similar (can't reliably determine the symbol range) - Fix: Failure to collect project ignore spec now logs the error and downstream tasks fail fast, fixing hanging LS initialisation - Improve loading of `project.yml` files: Gracefully handle user errors involving incorrect use of None/empty instead of list + - Project server: + - `query_project`: Support use of project root instead of project name #1388 + - `list_queryable_projects`: Return both project names and project roots * JetBrains: - `Move` and `SafeDelete` tools: transform empty string to None (counteracts client errors) diff --git a/src/serena/project_server.py b/src/serena/project_server.py index 11aa7b36..a227dc3b 100644 --- a/src/serena/project_server.py +++ b/src/serena/project_server.py @@ -53,7 +53,7 @@ class ProjectServer: serena_config.language_backend = LanguageBackend.LSP self._agent = SerenaAgent(serena_config=serena_config) - self._loaded_projects_by_name: dict[str, "Project"] = {} + self._loaded_projects_by_root: dict[str, "Project"] = {} # create the Flask application self._app = Flask(__name__) @@ -69,20 +69,23 @@ class ProjectServer: query_request = QueryProjectRequest.model_validate(request.get_json()) return self._query_project(query_request) - def _get_project(self, project_name: str) -> "Project": + def _get_project(self, project_root_or_name: str) -> "Project": """Gets the project with the given name, loading it if necessary.""" - if project_name in self._loaded_projects_by_name: - return self._loaded_projects_by_name[project_name] - else: - serena_config = self._agent.serena_config - registered_project = serena_config.get_registered_project(project_name) - if registered_project is None: - raise ValueError(f"Project '{project_name}' is not registered with Serena.") - with LogTime(f"Loading project '{project_name}'"): - project = registered_project.get_project_instance(serena_config) - project.create_language_server_manager() - self._loaded_projects_by_name[project_name] = project - return project + serena_config = self._agent.serena_config + registered_project = serena_config.get_registered_project(project_root_or_name) + if registered_project is None: + raise ValueError(f"Project '{project_root_or_name}' is not registered with Serena.") + + key = str(registered_project.project_root) + + if key in self._loaded_projects_by_root: + return self._loaded_projects_by_root[key] + + with LogTime(f"Loading project '{project_root_or_name}'"): + project = registered_project.get_project_instance(serena_config) + project.create_language_server_manager() + self._loaded_projects_by_root[key] = project + return project def _query_project(self, req: QueryProjectRequest) -> str: """Handle a /query_project request by invoking the agent on the specified project and tool.""" diff --git a/src/serena/tools/query_project_tools.py b/src/serena/tools/query_project_tools.py index b675a0aa..2820ab29 100644 --- a/src/serena/tools/query_project_tools.py +++ b/src/serena/tools/query_project_tools.py @@ -16,6 +16,7 @@ class ListQueryableProjectsTool(Tool, ToolMarkerOptional, ToolMarkerDoesNotRequi Lists available projects that can be queried with `query_project_tool`. :param symbol_access: whether to return only projects for which symbol access is available. Default: true + :return: project names and roots """ # determine relevant projects registered_projects = self.agent.serena_config.projects @@ -31,12 +32,9 @@ class ListQueryableProjectsTool(Tool, ToolMarkerOptional, ToolMarkerDoesNotRequi else: relevant_projects = registered_projects - # return project names, excluding the active project (if any) - project_names = [p.project_name for p in relevant_projects] - active_project = self.agent.get_active_project() - if active_project is not None: - project_names = [n for n in project_names if n != active_project.project_name] - return self._to_json(project_names) + # return project names and roots + result = {p.project_name: str(p.project_root) for p in relevant_projects} + return self._to_json(result) class QueryProjectTool(Tool, ToolMarkerOptional, ToolMarkerDoesNotRequireActiveProject): @@ -50,7 +48,7 @@ class QueryProjectTool(Tool, ToolMarkerOptional, ToolMarkerDoesNotRequireActiveP Queries a project by executing a read-only Serena tool. The tool will be executed in the context of the project. Use this to query information from projects other than the activated project. - :param project_name: the name of the project to query + :param project_name: the name of the project to query (or root path) :param tool_name: the name of the tool to execute in the other project. The tool must be read-only. :param tool_params_json: the parameters to pass to the tool, encoded as a JSON string """