* feat(solidlsp): add Astro language server support (T170)
* test(solidlsp): fix relative_paths type extraction in test_astro_basic
* refactor(solidlsp): replace asserts with explicit exceptions in Astro LSP
* docs(memories): document rule against assert in language server guide
* docs: add Astro language server documentation (README, docs, template, CHANGELOG)
* feat(solidlsp): implement dual-server architecture for Astro with companion TypeScript server (#2085)
- Implement companion AstroTypeScriptServer using @astrojs/ts-plugin for cross-file code intelligence between .ts and .astro components
- Route .ts/.js document symbols, definitions, references, and renames through companion TypeScript server
- Support two-way buffer edit forwarding between Astro LS and companion TS server
- Replace all assert statements with explicit SolidLSPException and FileNotFoundError
- Handle *.customData configuration requests for Volar HTML service
- Register .astro extension in src/serena/hooks.py
- Add test fixtures and tests for symbol retrieval and cross-file references from TypeScript to Astro components
- Update documentation and CHANGELOG
* fix(solidlsp): narrow astro_ls type to AstroLanguageServer in companion server test
* fix(solidlsp): prevent caching empty document symbols and poll initial compilation in Nextflow tests
Rename propagation
* `rename_memory_and_propagate_references` enumerated `get_full_list()`, which
includes the memories matched by `read_only_memory_patterns`.
* Writing one of those raises `PermissionError` in `_check_write_access`, so a
rename of a memory that was referenced from a read-only memory failed *after*
`move_memory` had already applied the rename.
* The memory graph was then half-updated: the renamed memory existed under its
new name while the writable referrers still held `mem:OLD_NAME`, and retrying
the rename failed with "Memory not found".
Fix
* In a tool context, propagate only into the memories that accept writes, sorted
to keep the enumeration order that `get_full_list()` provided.
* Outside a tool context nothing changes, so read-only memories are still updated
when the user renames through the CLI.
* A reference which thereby remains in a read-only memory is still reported as
stale by `validate_referential_integrity`, so it is not hidden.
Documentation
* `docs/02-usage/045_memories.md` promised that the tool rewrites every reference across all
memories. That is now only true for the memories the agent may write, so the caveat is stated
where the promise is made.
Tests
* Add a regression pair: the tool-context rename completes and rewrites both
occurrences in a writable referrer, while the CLI context additionally rewrites
the read-only one.
The previous default, 262.9593.0, is a JetBrains EAP-style build that has
expired: intellij-server exits at startup with "This build of intellij-server
has expired", so every Kotlin symbolic tool fails. 263.4702.0 is the newest
release on Kotlin/kotlin-lsp and starts normally.
Uses the same archive layout and CDN path as 262.9593.0. Hashes regenerated
with scripts/update_downloaded_dependency_hashes.py.
Fixes#2008
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Many case differentiations in the agent code were replaced by
method calls in the newly introduced LanguageBackend abstraction
* The registry allows new backends to be added dynamically
(via Python packages that implement a specific entrypoint)
The repository is licensed per component. SolidLSP (src/solidlsp,
test/solidlsp, test/resources) remains MIT-licensed and independently
reusable; the Serena application (src/serena, src/interprompt, scripts,
test/serena, docs) is licensed under GPL-3.0-or-later starting with the v2
licensing transition. The change is not retroactive: all releases and
commits up to v1.7.0 / 74c38a65 (tag mit-final) remain available under MIT.
Since MIT is GPL-compatible, a distribution combining both (such as the
serena-agent package) is as a whole subject to GPL-3.0-or-later, while the
SolidLSP files themselves stay MIT and can be extracted and used separately
under MIT terms. The distribution metadata therefore declares
GPL-3.0-or-later, with both license texts shipped alongside it.
Serena originally began under the GPL (v2) and was switched to MIT in
May 2025 following community requests. We consider that change a mistake;
the substantial changes in v2 make this the appropriate time to revert it.
We want the best version of Serena to remain free.
Changes:
* LICENSE is now the licensing overview; canonical license texts live in
LICENSES/ (MIT.txt is the previous LICENSE verbatim, GPL-3.0-or-later.txt
is the unmodified FSF text)
* pyproject.toml declares the PEP 639 license expression
"GPL-3.0-or-later" and bundles LICENSE and LICENSES/* as license files;
the deprecated MIT classifier is dropped and flake.nix declares gpl3Plus;
README has per-component license badges and a License section
* SPDX-License-Identifier headers in all Python sources under src/ and
scripts/, added by the new idempotent scripts/add_spdx_headers.py, which
gen_prompt_factory.py also uses to keep the header on the generated
module; existing third-party notices are preserved
* CLA.md: Contributor License Agreement (contributor retains copyright;
grants a perpetual, irrevocable license including relicensing under any
terms, incl. proprietary/commercial; patent grant; authority
representations), to be enforced repository-wide via cla-assistant.io
* CONTRIBUTING.md, PR template and a new docs page explain the licensing
boundary and the CLA workflow
* Apply object-oriented design
* Establish common interface via protocol LanguageServerIdLike
* Move all registration concerns to LanguageServerRegistry
Codex's documented hook wiring only routes `serena-hooks remind` through
PreToolUse on the `Bash` matcher, so `ToolUseCounter.update()`'s
reset-on-Serena-tool-use branch is never reached there: it only fires when
`remind` itself is invoked for a `mcp__serena__*` tool name, which the
Bash-only matcher excludes. Reminder counters therefore never clear after a
successful Serena call, and an unrelated grep/read burst afterwards can trip
the deny threshold on state that should have been reset.
Add a `serena-hooks reset` command, wired to PostToolUse on Serena's own
tools, that resets the counters after a successful call. Gated on the call
having succeeded (`tool_response` carrying no `isError: true`, the MCP
`tools/call` result shape) so a failed Serena call does not mask a real
grep/read streak, per the issue's own acceptance criteria. The symbolic-tool
classification is shared with the existing PreToolUse hook via a small
extracted helper so both agree on what counts as a Serena tool.
Fixes#1852
Cross-file queries waited 5s after the first file was opened. On a cold Metals
that is long before it has imported the build, indexed, and — crucially —
compiled: references come from SemanticDB, which the build server writes as it
compiles, so a query made in between returns some of the references rather than
none, with nothing to mark it partial. Against a ~700-file Scala 3 build the
first find-references returned 124 of 242.
Metals already reports all of this as work-done progress; Serena did not declare
`window.workDoneProgress`, so none of it arrived. Declare it, track the tokens,
and wait for them before the first cross-file query — the same event-based shape
as TypeScript's indexing wait (#1639), whose hooks this uses.
Readiness is "nothing outstanding for a moment", not "nothing outstanding":
Metals hands off between its phases rather than overlapping them, so the token
set empties for around a second between the import ending and indexing starting,
and a plain drain returns in that gap. Measured on the same build, the first
find-references now returns all 242, taking 32s rather than 22s.
Adds support for Wolfram Language (.wl, .wls files) using the official
WolframResearch LSPServer paclet, which communicates via stdio.
Requires Wolfram Mathematica 13.0+ or Wolfram Engine 12.1+.
The WolframKernel is located via WOLFRAM_PATH, the system PATH, common
install locations, or ls_path in ls_specific_settings.
Includes language server implementation, test repo, test suite
(skipped gracefully when WolframKernel is unavailable), and
documentation updates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Metals serves one build per workspace folder, and Serena sends only the
repository root. Where the builds live below the root, Metals falls back to
its own search (BuildTools.searchForBuildTool), which looks one level down
and takes the *first* match — so in a monorepo every build but that one is
served with no build target, and cross-file references silently come back
empty.
Detect the build roots instead and send them all, one Metals service each.
`ls_specific_settings.scala.project_roots` names them explicitly where the
detection guesses wrong, `project_root_scan_depth` bounds the search.
The configured `ls_workspace_folders` are not usable for this: they are about
what SolidLSP indexes and are shared by every language server of a project, so
in a polyglot monorepo no single value suits both Metals and, say, tsserver.
Where the repository root is itself a build root, nothing changes.
The Nextflow language server schedules a debounced (1s) AST update on every
didOpen/didChange, and LanguageService.references is the only request that does
not await it -- documentSymbol, codeLens, documentLink and semanticTokensFull
all do. A references request issued inside that window races the recompile of
the file it asks about and can return an empty list, which is what the macOS CI
runner hit. Send a documentSymbol request for the same file first, which blocks
server-side until the pending update has been applied.
Also compare reference paths using the platform separator in the tests, since
relativePath is OS-native and the hardcoded forward slashes failed on Windows.
* scala: answer Metals' build-import prompt
Metals asks, via window/showMessageRequest, whether to import a workspace it
has not seen before. Serena registered no handler, so the request came back
`method 'window/showMessageRequest' not handled on client` and Metals logged
"Unexpected error initializing server" and gave up. No build server, no build
target, and every cross-file query answered by the fallback presentation
compiler — which cannot see past the file it is given.
A client cannot decline the question instead: Metals' `disableShowMessageRequest`
is server-side configuration, and its no-op fallback answers "Not now", which
imports nothing either. Answering is the only route to a build.
So answer the three prompts that lead to a build server, and dismiss anything
else with `null` — a prompt we do not recognise is one whose consequences we
cannot judge, and two of Metals' others offer to kill a process and to open a
window. "Don't show again" is never chosen; Metals persists that in the
project's own state.
Since answering yes lets Metals run the project's build tool,
`ls_specific_settings.scala.auto_import_build: false` declines instead.
* scala: name the build-tool choice as a gap, and test the setting
`Messages.ChooseBuildTool` ("Multiple build definitions found. Which would
you like to use?") offers the build tools' own executable names, and precedes
the import prompt wherever a workspace holds more than one kind of build. It
is dismissed like anything else unrecognised, so such a workspace is still
not imported — a deliberate choice, since picking one is a guess of a
different order, but one the comment and the setup guide should admit to
rather than claim every prompt on the path is answered.
Also: cover the route from `ls_specific_settings` to `auto_import_build`,
which nothing exercised, and correct the `ImportBuildChanges` message in the
fixtures, which had the notification variant's trailing full stop rather than
the request's own text.
* feat(kotlin): update language server to 262.9593.0
Note: download URL structure differs depending on version
---------
Co-authored-by: Dominik Jain <dominik.jain@oraios-ai.de>
Add a Deno language server backed by the Deno CLI's built-in `deno lsp`,
serving TypeScript/JavaScript in Deno projects. Unlike the plain
typescript-language-server it understands Deno module resolution
(npm:/jsr:/https: imports) and the `Deno.*` global namespace.
It is experimental and must be selected explicitly via
`language_servers: [deno]`; it overlaps the TypeScript server on file
extensions, so it is not auto-detected. Requires the `deno` CLI on PATH.
Tests run in CI: the `deno` marker joins the other-langs batch, which now
installs Deno via denoland/setup-deno. Off-CI and wherever the CLI is
absent they skip through the central conftest guard, following the existing
pattern for toolchain-gated language servers. Hover is covered explicitly,
including hover on a `Deno.*` global, which is the capability this server
adds over the TypeScript one.
Erlang LS identifies functions, types and parameterised macros as
`name/arity`, but `/` separates the components of a Serena name path, so
`create_user/4` was parsed as "symbol `4` nested inside `create_user`" and
could never match -- not even via the name path Serena itself reported for
the symbol. Browsing still worked, but find_referencing_symbols,
replace_symbol_body and insert_after_symbol were unusable on Erlang
functions.
Normalize the name to `create_user#4` instead. `#` cannot occur in an
unquoted Erlang atom, so it can never collide with a real name, unlike `@`.
The arity is kept rather than stripped because it is part of a function's
identity in Erlang: create_order/3 and create_order/2 are different
functions and may coexist in one module.
Also document the "no `/` in symbol names" rule on _normalize_symbol_name,
which is what the next backend for such a language needs to know.
Several backends that are registered and tested were absent from the
user-facing lists: six from the README (pascal, qml, rego, systemverilog,
terraform, vue), six from the Language Support docs page (matlab,
powershell, rego, systemverilog, terraform, toml), and two from the
commented language-server list in the project template
(python_basedpyright, qml).
The drift is long-standing and runs in both directions: matlab,
powershell and toml reached the README but never the docs page; pascal,
vue and qml reached the docs page but never the README; terraform, rego
and systemverilog reached neither, the oldest of them since 2025-07-06.
The two template omissions are the most recent, from #1705 and #1635.
The guide's Documentation step does ask for the README and the docs page,
so most of this was missed rather than unspecified; it does not mention
the project template at all.
The setup notes on the new docs-page entries name the versions Serena
actually pins, taken from the corresponding language-server modules:
MATLAB extension 1.3.9, PowerShell Editor Services 4.4.0 and
PSScriptAnalyzer 1.25.0, Verible v0.0-4051-g9fdb4057, terraform-ls
0.36.5, and Taplo 0.10.0. The PowerShell entry mentions both automatic
installations, since enabling that backend also runs Save-Module against
the user's configured PowerShell repository.
The new Regal link points at open-policy-agent/regal, the current
canonical repository; StyraInc/regal still redirects there, and the
adapter and the CI workflow have not been changed.
The template block is refreshed from scripts/print_language_list.py,
which is why its column widths change: python_basedpyright is longer than
any identifier the previous list contained. The pointer directly below it
still named the Language enum, renamed to LanguageServerId in d2cf18dd.
find_referencing_symbols/request_references could return incomplete results on
large TypeScript projects. _wait_for_indexing_start_or_completion waits only
_INDEXING_START_GRACE_S (hardcoded 2.0s) for tsserver to *start* emitting
$/progress after opening files; if nothing has appeared by then, it assumes no
indexing is needed and proceeds immediately. On a large project, tsserver can
take longer than that just to resolve the project graph before it even creates
the first progress token, so the first cross-file reference query can race a
project that is still loading.
Every caller of _wait_for_indexing_start_or_completion (the base TypeScript
path and the Svelte companion) always calls expect_indexing() first, so the
"no progress observed" branch is reached specifically when indexing is already
expected; it cannot distinguish "nothing to do" from "not started yet",
because the two look identical from the client for as long as tsserver stays
quiet. That ambiguity cannot be resolved from client-observable signals alone,
so this makes the grace an ls_specific_settings knob (`indexing_start_grace`),
the same way indexing_timeout and server_ready_timeout already are, and raises
the default from 2.0s to 5.0s.
Fixes#1586
Adds first-class support for xAI's Grok Build CLI, following the
existing client-integration pattern (Claude Code, CodeBuddy, Codex):
- grok context: single-project, shaped like claude-code.yml, with the
file-tool exclusions appropriate for a CLI agent that has its own
read/edit/shell tools. structured_tool_output is left at the auto
default (as in the codex and codebuddy contexts); claude-code's
explicit false is a Claude-Code-specific bug workaround that is not
carried over.
- serena setup grok: registers Serena as a user-scoped MCP server via
"grok mcp add" after detecting a compatible grok CLI.
- serena-hooks --client=grok: Grok-native PreToolUse output
({decision, reason}) instead of the Claude-style hookSpecificOutput
envelope; grep/read detection extended to Grok's tool names and
shell payloads (target_file/targetFile path keys, non-string command
values handled defensively). The payload-parsing refinements live in
the remind hook's client-agnostic parsing and apply to all hook
clients: a non-string command value previously raised, and
target_file/targetFile paths were not recognized.
- Docs (030_clients.md #grok), unit tests for setup/hooks/context, and
scripts/live_test_grok.py - a zero-inference, state-preserving live
smoke test against a real grok installation (documented in
CONTRIBUTING.md). Its M5 check verifies the structured-output wire
shape: the grok context (auto default) serves an output schema and
structuredContent for string-returning tools, while the claude-code
context (explicit false workaround) serves plain text only.
- PHP/Intelephense: expose `file_filter` via `ls_specific_settings["php"]`, so that additional
extensions containing PHP sources (e.g. Drupal's `.module` / `.install` / `.inc` / `.theme`)
become visible to the symbol tools and are indexed by the language server
- PHP: treat `.phtml` files as PHP sources by default (all PHP language servers)
* Java (JDT-LS): allow registering additional JRE/JDK runtimes via config
JDT-LS resolves each imported module's JRE_CONTAINER/.../JavaSE-NN/
classpath entry against its own "Installed JREs" list, which Serena
previously hardcoded to a single JavaSE-21 entry pointing at the
bundled JRE. Projects whose source/target level exceeds that bundled
JDK (e.g. sourceCompatibility = VERSION_25) get a container that never
resolves, so every JDK type -- java.lang.Object, java.util.*, etc. --
comes back "cannot be resolved", silently breaking diagnostics and
cross-module resolution. There was no config key to tell JDT-LS about
an installed newer JDK.
Add an optional `runtimes` list under ls_specific_settings.java,
mirroring VS Code's java.configuration.runtimes shape (name, path,
optional default/sources/javadoc). Entries are validated (name+path
required, path must exist) via the new _resolve_configured_runtimes(),
then merged with the bundled JavaSE-21 default: configured runtimes
extend the list; an entry reusing the "JavaSE-21" name overrides the
bundled one; if a configured entry claims `default`, the bundled
runtime's own default flag is cleared so JDT-LS doesn't see two
defaults. The `runtimes` key is also added to the JDTLS workspace-hash
inputs so changing it lands in a fresh workspace instead of reusing a
stale Buildship/Maven import.
Testing: added 14 unit tests in
test/solidlsp/java/test_jdtls_path_resolution.py covering
_resolve_configured_runtimes validation (missing keys, wrong types,
nonexistent paths) and the merge behavior in
_create_base_initialize_params (extension, name-collision override,
default hand-off). Ran `poe format`, `poe type-check`, and
`pytest test/solidlsp/java -m "not java"` (69 passed, 8 deselected
real-JDTLS tests that require a JDK) locally.
Fixes#1478
* docs: document java.runtimes in configuration guide
Address review feedback on #1715 by documenting ls_specific_settings.java.runtimes
in 050_configuration.md (settings table, note, and example).
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: arimu1 <19286898+arimu1@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Perl::LanguageServer only indexes files whose extension is in its
`perl.fileFilter` and skips directories in `perl.ignoreDirs`. Both were
hardcoded in PerlLanguageServer, so projects whose Perl source uses
non-standard extensions (e.g. .cgi / .psgi web handlers, which can
dominate a mature codebase) had no way to make those files visible to
symbol/reference queries without forking Serena (#1449).
Surface both lists through `ls_specific_settings["perl"]` (keys
`file_filter` and `ignore_dirs`), defaulting to the existing values so
behaviour is unchanged for current projects. The resolution lives in a
pure static method `_resolve_filter_settings` so the config plumbing is
covered by unit tests that don't require a Perl runtime.
Keep Serena's symbol index in sync with the LS (#1449, per review):
`find_symbol` and symbol indexing are driven by
`Language.PERL.get_source_fn_matcher()`, so adding an extension (e.g.
.cgi) to `file_filter` alone would leave the matcher untouched and the
extension's symbols invisible. Since that matcher is now a @cache'd
per-language singleton, add a `FilenameMatcher.add_extensions()` mutator
and call it from `PerlLanguageServer` so the configured extensions also
reach the symbol index, the ignore checks, and language composition.
Avoid cross-project leakage (per review): because the matcher is a
process-wide singleton, extensions added for one project would otherwise
persist into the next. `FilenameMatcher` now snapshots its initial
configuration and exposes `reset()`, and `SolidLanguageServer.__init__`
resets the matcher right after `self.language` is determined, so every
activation starts from the language's defaults before the subclass
re-applies its own `file_filter`.
This complements 860c5841 (which added .t to the default list as a
surface-level fix) by addressing the root configurability gap that the
maintainer acknowledged in #1449.
Make the Svelte companion TypeScript server raise on readiness and
indexing timeouts instead of silently proceeding with a partial index,
turning flaky/wrong cross-file results into clear failures:
- TypeScriptLanguageServer: timeout handling factored into overridable
hooks (_handle_server_ready_timeout, _handle_project_indexing_timeout);
the base server keeps its historical permissive behavior, so plain
TypeScript setups are unchanged.
- SvelteTypeScriptServer: both overrides raise TimeoutError; only the
indexing-timeout error includes describe_indexing_state(), instead of
serving requests from a cold or partially indexed program.
- Configurable server_ready_timeout / indexing_timeout via
ls_specific_settings.
- SvelteLanguageServer: file-open failures during companion preparation
raise SvelteCompanionPreparationError with a count-first file listing
(capped at 10), chained to the first underlying error so the cause is
visible without DEBUG logging.
Apply the same fail-loudly principle to the test fixture bootstrap:
- Svelte test repo: npm ci with a committed package-lock.json, removing
dependency-resolution non-determinism from CI runs.
- Run svelte-kit sync explicitly during fixture setup and fail loudly if
.svelte-kit/tsconfig.json cannot be generated. The fixture's "prepare"
script masks sync failures ("svelte-kit sync || echo ''"), and a known
npm bug (npm/cli#4828) can silently skip platform-specific optional
dependencies; the result is unresolvable $lib path aliases and
cross-file tests failing with partial results instead of a clear,
actionable error.
- codespell: skip third-party fixture repositories, using the anchored
pattern ./test/resources/repos (codespell matches skip globs against
the ./-prefixed paths it walks, so an anchor-less pattern never
matches).
Also add direct regression tests for the timeout-policy split
(test/solidlsp/test_typescript_timeout_policy.py: base stays
permissive, companion raises, settings plumbing and precedence, and
start-or-completion indexing-wait semantics). The tests use no mocking or
patching of any kind; a hand-written call-recording fake drives the companion
guard against real file discovery under a pytest tmp_path. They spawn no LS
process and avoid external readiness races; one wait test uses a real Timer
with a 30s ceiling.
The configuration docs state that the TypeScript table lists both knobs; the
Svelte table lists indexing_timeout, with readiness inheritance documented in
prose.