Skip to content

Commit db49ab7

Browse files
authored
Merge branch 'main' into feat/svelte
2 parents 4f431db + fc27a52 commit db49ab7

17 files changed

Lines changed: 478 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,16 @@ Status of the `main` branch. Changes prior to the next official version change w
2626
- `query_project`: Support use of project root instead of project name #1388
2727
- `list_queryable_projects`: Return both project names and project roots
2828
- Fix: `search_for_pattern` tool returned 1-based line numbers (in contrast to all other tools); cause: implementation of `text_utils.search_text`
29+
- Serena's system prompt (a.k.a. the 'Serena Instructions Manual') is now provided lazily.
30+
At MCP connection time, only a one-sentence bootstrap prompt is provided.
31+
The `initial_instructions` tool provides the full prompt on demand, keeping the initial context lean.
2932

3033
* JetBrains:
3134
- `Move` and `SafeDelete` tools: transform empty string to None (counteracts client errors)
3235

3336
* Dependencies:
3437
- `pywebview`: Switch back to official release (new version 6.2) #1253
38+
- `mcp`: Update from `1.26.0` to `1.27.0`
3539

3640
* Evaluations:
3741
- Added new evaluations for Junie Plugin with Opus 4.6 and GLM 5.1 in Claude Code.
@@ -41,6 +45,8 @@ Status of the `main` branch. Changes prior to the next official version change w
4145
- Fix: `rename_symbol` for Vue files now correctly propagates edits to the TypeScript server, enabling cross-file renames in `.vue` files
4246
- Add Svelte language server support (Svelte 5 + SvelteKit via `svelte-language-server`; auto-installed via npx; supports `.svelte`, `.ts`, and `.js` files)
4347
- Fix: Lean4 stale cache — empty document symbol responses (returned before `lake build` completes) are no longer persisted, preventing symbols from being permanently hidden #1356
48+
- Add JSON language server support via `vscode-json-languageserver` (experimental) #1391
49+
- Fix: Elixir/Expert deadlock on startup — Expert's build pipeline requires a `textDocument/didOpen` notification to start; Serena now opens `mix.exs` immediately after `initialized` so Expert begins compiling instead of waiting indefinitely #1397
4450

4551
Dashboard:
4652
- Add configurable dashboard interface mode (new global configuration setting `web_dashboard_interface`):

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,11 @@ Serena incorporates a powerful abstraction layer for the integration of language
110110
The underlying language servers are typically open-source projects or at least freely available for use.
111111

112112
When using Serena's language server backend, we provide **support for over 40 programming languages**, including
113+
<<<<<<< feat/svelte
113114
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, Svelte, Swift, TOML, TypeScript, Vue, WGSL, YAML, and Zig.
115+
=======
116+
AL, Ansible, Bash, C#, C/C++, Clojure, Crystal, Dart, Elixir, Elm, Erlang, Fortran, F#, GLSL, Go, Groovy, Haskell, Haxe, HLSL, Java, JavaScript, JSON, 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.
117+
>>>>>>> main
114118
115119
### The Serena JetBrains Plugin
116120

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ Some languages require additional installations or setup steps, as noted.
117117
* **Vue**
118118
(3.x with TypeScript; requires Node.js v18+ and npm; supports .vue Single File Components with monorepo detection)
119119
* **YAML**
120+
* **JSON**
121+
(experimental; must be explicitly added to the languages list; requires Node.js and npm)
120122
* **Zig**
121123
(requires installation of ZLS - Zig Language Server)
122124

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ dependencies = [
2121
"pyright==1.1.403",
2222
"fortls==3.2.2",
2323
"overrides==7.7.0",
24-
"python-dotenv==1.2.1",
25-
"mcp==1.26.0",
24+
"python-dotenv==1.2.2",
25+
"mcp==1.27.0",
2626
"flask==3.1.3", # bumped from 3.1.1 for CVE fix (also fixes werkzeug alert)
2727
"sensai-utils==1.5.0",
2828
"pydantic==2.12.5",
@@ -337,6 +337,7 @@ markers = [
337337
"haskell: Haskell language server tests",
338338
"haxe: Haxe language server tests",
339339
"yaml: language server running for YAML",
340+
"json: language server running for JSON (uses vscode-json-languageserver)",
340341
"powershell: language server running for PowerShell",
341342
"pascal: language server running for Pascal (Free Pascal/Lazarus)",
342343
"cpp: language server running for C/C++",

src/serena/agent.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -891,8 +891,18 @@ def _format_prompt(self, prompt_template: str) -> str:
891891
template = JinjaTemplate(prompt_template)
892892
return template.render(available_tools=self._exposed_tools.tool_names, available_markers=self._exposed_tools.tool_marker_names)
893893

894+
def create_connection_prompt(self) -> str:
895+
"""
896+
Returns the bootstrap prompt to be sent at MCP connection time.
897+
898+
:return: the prompt
899+
"""
900+
return self.prompt_factory.create_connection_prompt()
901+
894902
def create_system_prompt(self, session_id: str = "global") -> str:
895903
"""
904+
Returns the 'Serena Instructions Manual', i.e. Serena's system prompt.
905+
896906
:param session_id: the client session ID for the case where this is run from a tool; "global" for the connection time case
897907
:return: the prompt
898908
"""
@@ -925,7 +935,6 @@ def create_system_prompt(self, session_id: str = "global") -> str:
925935
if self._active_project is not None and not self._project_prompt_status.is_project_activation_message_already_provided(session_id):
926936
system_prompt += "\n\n" + self.get_project_activation_message(session_id)
927937

928-
log.info("System prompt:\n%s", system_prompt)
929938
return system_prompt
930939

931940
def get_project_activation_message(self, session_id: str) -> str:

src/serena/generated/generated_prompt_factory.py

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,8 @@ class PromptFactory(PromptFactoryBase):
1717
def create_onboarding_prompt(self, *, system: Any) -> str:
1818
return self._render_prompt("onboarding_prompt", locals())
1919

20-
def create_think_about_collected_information(self) -> str:
21-
return self._render_prompt("think_about_collected_information", locals())
22-
23-
def create_think_about_task_adherence(self) -> str:
24-
return self._render_prompt("think_about_task_adherence", locals())
25-
26-
def create_think_about_whether_you_are_done(self) -> str:
27-
return self._render_prompt("think_about_whether_you_are_done", locals())
28-
29-
def create_summarize_changes(self) -> str:
30-
return self._render_prompt("summarize_changes", locals())
31-
32-
def create_prepare_for_new_conversation(self) -> str:
33-
return self._render_prompt("prepare_for_new_conversation", locals())
20+
def create_connection_prompt(self) -> str:
21+
return self._render_prompt("connection_prompt", locals())
3422

3523
def create_system_prompt(
3624
self,

src/serena/mcp.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ def create_mcp_server(
332332
# retain only FASTMCP_ prefix for already set environment variables.
333333
Settings.model_config = SettingsConfigDict(env_prefix="FASTMCP_")
334334
instructions = self._get_initial_instructions()
335+
log.info("MCP server initial instructions:\n%s", instructions)
335336
mcp = FastMCP(
336337
name="Serena",
337338
lifespan=self.server_lifespan,
@@ -357,4 +358,4 @@ async def server_lifespan(self, mcp_server: FastMCP) -> AsyncIterator[None]:
357358

358359
def _get_initial_instructions(self) -> str:
359360
assert self.agent is not None
360-
return self.agent.create_system_prompt()
361+
return self.agent.create_connection_prompt()

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# in which case this prompt will be given as a regular message on the call of a simple tool which the agent
33
# is encouraged (via the tool description) to call at the beginning of the conversation.
44
prompts:
5+
connection_prompt: |
6+
CRITICAL: Before starting to work on a coding task, call the `initial_instructions` tool to read the 'Serena Instructions Manual'.
57
system_prompt: |
68
You have access to semantic coding tools upon which you rely heavily for all your work.
79
You operate in a resource-efficient and intelligent manner, always keeping in mind to not read or generate

src/solidlsp/language_servers/elixir_tools/elixir_tools.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,28 @@ def publish_diagnostics(params: Any) -> None:
360360

361361
self.server.notify.initialized({})
362362

363+
# Expert's build pipeline is triggered by a textDocument/didOpen notification.
364+
# Without it Expert sits idle and never emits the $/progress signals we wait for,
365+
# causing a deadlock. Opening mix.exs is the minimal trigger; we close it again
366+
# once the server is ready so it does not linger in the open-file set.
367+
mix_exs_path = os.path.join(self.repository_root_path, "mix.exs")
368+
mix_exs_uri = pathlib.Path(mix_exs_path).as_uri() if os.path.exists(mix_exs_path) else None
369+
370+
if mix_exs_uri is not None:
371+
with open(mix_exs_path, encoding="utf-8") as f:
372+
mix_exs_content = f.read()
373+
self.server.notify.did_open_text_document(
374+
{
375+
"textDocument": {
376+
"uri": mix_exs_uri,
377+
"languageId": "elixir",
378+
"version": 1,
379+
"text": mix_exs_content,
380+
}
381+
}
382+
)
383+
log.debug("Opened mix.exs to trigger Expert's build pipeline")
384+
363385
# Expert needs time to compile the project and build indexes on first run.
364386
# This can take 2-3+ minutes for mid-sized codebases.
365387
# After the first run, subsequent startups are much faster.
@@ -370,3 +392,7 @@ def publish_diagnostics(params: Any) -> None:
370392
else:
371393
log.warning(f"Expert did not signal readiness within {ready_timeout}s. Proceeding with requests anyway.")
372394
self.server_ready.set() # Mark as ready anyway to allow requests
395+
396+
if mix_exs_uri is not None:
397+
self.server.notify.did_close_text_document({"textDocument": {"uri": mix_exs_uri}})
398+
log.debug("Closed mix.exs after Expert is ready")
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
"""
2+
Provides JSON specific instantiation of the LanguageServer class using vscode-json-languageserver.
3+
Contains various configurations and settings specific to JSON files.
4+
"""
5+
6+
import logging
7+
import os
8+
import pathlib
9+
import shutil
10+
from typing import Any
11+
12+
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection, build_npm_install_command
13+
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
14+
from solidlsp.ls_config import LanguageServerConfig
15+
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
16+
from solidlsp.settings import SolidLSPSettings
17+
18+
log = logging.getLogger(__name__)
19+
20+
21+
class JsonLanguageServer(SolidLanguageServer):
22+
"""
23+
Provides JSON specific instantiation of the LanguageServer class using vscode-json-languageserver.
24+
Contains various configurations and settings specific to JSON files.
25+
26+
Note: Cross-file references are not supported for JSON (the language server only provides
27+
document symbols and hover). JSON is useful for getting a structured overview of JSON files
28+
and navigating their contents.
29+
"""
30+
31+
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
32+
"""
33+
Creates a JsonLanguageServer instance. This class is not meant to be instantiated directly.
34+
Use LanguageServer.create() instead.
35+
"""
36+
super().__init__(
37+
config,
38+
repository_root_path,
39+
None,
40+
"json",
41+
solidlsp_settings,
42+
)
43+
44+
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
45+
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
46+
47+
class DependencyProvider(LanguageServerDependencyProviderSinglePath):
48+
def _get_or_install_core_dependency(self) -> str:
49+
"""
50+
Setup runtime dependencies for JSON Language Server and return the path to the executable.
51+
"""
52+
is_node_installed = shutil.which("node") is not None
53+
assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
54+
is_npm_installed = shutil.which("npm") is not None
55+
assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
56+
57+
json_language_server_version = self._custom_settings.get("json_language_server_version", "1.3.4")
58+
npm_registry = self._custom_settings.get("npm_registry")
59+
60+
deps = RuntimeDependencyCollection(
61+
[
62+
RuntimeDependency(
63+
id="vscode-json-languageserver",
64+
description="vscode-json-languageserver package (Microsoft)",
65+
command=build_npm_install_command("vscode-json-languageserver", json_language_server_version, npm_registry),
66+
platform_id="any",
67+
),
68+
]
69+
)
70+
71+
json_ls_dir = os.path.join(self._ls_resources_dir, "json-lsp")
72+
json_executable_path = os.path.join(json_ls_dir, "node_modules", ".bin", "vscode-json-languageserver")
73+
74+
if os.name == "nt":
75+
json_executable_path += ".cmd"
76+
77+
if not os.path.exists(json_executable_path):
78+
log.info(f"JSON Language Server executable not found at {json_executable_path}. Installing...")
79+
deps.install(json_ls_dir)
80+
log.info("JSON language server dependencies installed successfully")
81+
82+
if not os.path.exists(json_executable_path):
83+
raise FileNotFoundError(
84+
f"vscode-json-languageserver executable not found at {json_executable_path}, something went wrong with the installation."
85+
)
86+
87+
return json_executable_path
88+
89+
def _create_launch_command(self, core_path: str) -> list[str]:
90+
return [core_path, "--stdio"]
91+
92+
@staticmethod
93+
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
94+
"""
95+
Returns the initialize params for the JSON Language Server.
96+
"""
97+
root_uri = pathlib.Path(repository_absolute_path).as_uri()
98+
initialize_params = {
99+
"locale": "en",
100+
"capabilities": {
101+
"textDocument": {
102+
"synchronization": {"didSave": True, "dynamicRegistration": True},
103+
"completion": {"dynamicRegistration": True, "completionItem": {"snippetSupport": True}},
104+
"definition": {"dynamicRegistration": True},
105+
"references": {"dynamicRegistration": True},
106+
"documentSymbol": {
107+
"dynamicRegistration": True,
108+
"hierarchicalDocumentSymbolSupport": True,
109+
"symbolKind": {"valueSet": list(range(1, 27))},
110+
},
111+
"hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
112+
},
113+
"workspace": {
114+
"workspaceFolders": True,
115+
"didChangeConfiguration": {"dynamicRegistration": True},
116+
},
117+
},
118+
"processId": os.getpid(),
119+
"rootPath": repository_absolute_path,
120+
"rootUri": root_uri,
121+
"workspaceFolders": [
122+
{
123+
"uri": root_uri,
124+
"name": os.path.basename(repository_absolute_path),
125+
}
126+
],
127+
}
128+
return initialize_params # type: ignore
129+
130+
def _start_server(self) -> None:
131+
"""
132+
Starts the JSON Language Server, waits for the server to be ready and yields the LanguageServer instance.
133+
"""
134+
135+
def register_capability_handler(params: Any) -> None:
136+
return
137+
138+
def do_nothing(params: Any) -> None:
139+
return
140+
141+
def window_log_message(msg: dict) -> None:
142+
log.info(f"LSP: window/logMessage: {msg}")
143+
144+
self.server.on_request("client/registerCapability", register_capability_handler)
145+
self.server.on_notification("window/logMessage", window_log_message)
146+
self.server.on_notification("$/progress", do_nothing)
147+
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
148+
149+
log.info("Starting JSON server process")
150+
self.server.start()
151+
initialize_params = self._get_initialize_params(self.repository_root_path)
152+
153+
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
154+
init_response = self.server.send.initialize(initialize_params)
155+
log.debug(f"Received initialize response from JSON server: {init_response}")
156+
157+
if "documentSymbolProvider" in init_response.get("capabilities", {}):
158+
log.info("JSON server supports document symbols")
159+
else:
160+
log.warning("Warning: JSON server does not report document symbol support")
161+
162+
self.server.notify.initialized({})
163+
164+
log.info("JSON server initialization complete")

0 commit comments

Comments
 (0)