search_for_pattern resolved each match's line number by rescanning the
file from the beginning (TextUtils.get_line_from_index walks a
TextStepper from index 0), making coordinate resolution O(n) per match
and O(n*m) for m matches. search_text now precomputes line start
offsets once per file (O(n)) and resolves each match via binary search
(O(log n) per match).
The precomputed table mirrors TextStepper line semantics exactly:
\n, \r\n and bare \r are line separators, and an index pointing at the
\n of a \r\n pair resolves to (next line, 0) — the same rule
TextUtils.get_line_col_from_index implements, now pinned by regression
tests (including the mixed line-ending edge cases from the
insert_text_at_position fix).
On a synthetic 12k-line file (494 KB) with 723 matches, match-coordinate
resolution drops from ~1764 ms to ~2 ms (~1000x); the one-off
precompute costs ~1.1 ms. Small files regress nothing (100 lines, 5
matches: 70 µs -> 11 µs). Results are byte-for-byte identical to the
previous implementation.
Note: compatible with PR #1899 (interruptible agent regexes): that PR
touches the re.compile call site in search_text and the ContentReplacer
helpers, this PR touches the post-match line-coordinate resolution; the
two changes are complementary and rebase cleanly onto each other.
* perf(search_text): add reproducible benchmark script
scripts/profile_search_text.py regenerates the synthetic benchmark
content with a fixed seed (no real-world code), asserts both resolution
paths produce identical results, and prints the before/after timings
quoted in the PR description. Default: 12k lines / ~1830 matches;
optional CLI args scale the file down for quick runs.
* refactor(ls): encapsulate cached text coordinates in TextCoordinates abstraction
Address review feedback on the search_text line-lookup PR: the previously
added module-level helpers duplicated TextStepper's line-ending logic and
exposed raw offsets as a low-level data structure shared between two
functions, violating the project's software design principles (each
concern in exactly one home; dataclasses instead of tuples for simple
data storage).
Introduce in solidlsp/ls_utils.py:
- LineCol: frozen, kw-only dataclass for a 0-based line/column position
- TextCoordinates: encapsulates the cached table of line start offsets,
built via TextStepper (step_line + line_start_idx), with a single
public method line_col_at_index() resolving an index via binary
search, including the CRLF rule (an index pointing at the '\n' of a
'\r\n' pair denotes the beginning of the next line, column 0) and
InvalidTextLocationError for out-of-range indices
search_text now instantiates TextCoordinates once per file and resolves
each match's coordinates through the public method; the private helpers
(_compute_line_starts, _line_col_at_index) are removed.
Equivalence with TextUtils.get_line_col_from_index verified across
mixed line endings, empty content and boundary indices (0 mismatches).
Tests are behaviour-anchored: the implementation-coupled parametrized
test importing the private helpers is replaced by public-contract tests
of TextCoordinates (test/solidlsp/test_ls_utils.py) and absolute line
assertions through the public search_text API. The benchmark script
uses the public API as well.
Benchmark (synthetic 12k lines, ~723 matches, best of 5): coordinate
resolution ~1764 ms -> ~2 ms; precompute ~1.1 ms; small files unchanged.
Semantics are unchanged; results are identical to the previously
proposed implementation and to the upstream reference implementation.
* fix(test): rename test_ls_utils to test_text_coordinates to avoid basename collision
The new test file collided with the pre-existing upstream
test/solidlsp/util/test_ls_utils.py: pytest (prepend import mode, no
__init__.py in the test dirs) requires unique basenames, and collecting
both modules produced an import-file-mismatch error that aborted
collection with exit code 2 across every CI matrix job.
Rename to test_text_coordinates.py (unique, and matches the class under
test). Full-suite collection verified locally (2252 tests, 0 errors).
* 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)
bump_version could previously only create releases. It now provides two
subcommands, and the mutually exclusive --major/--minor/--patch flags (as
well as the explicit --version option) are replaced by a positional
argument, which cannot be misused:
bump_version.py release <current|major|minor|patch>
bump_version.py dev <major|minor|patch>
`dev` bumps the version to a new .dev0 version and commits it as
"Set version to vX" without creating a tag or touching the changelog. This
allows work on main to target a new minor/major version independently of a
release.
Because a .dev0 version no longer necessarily reserves the next patch
version, `release` now takes the target "current", which releases the
version reserved by the current .dev version (the usual case), whereas
major/minor/patch bump beyond it. The former special case of not
incrementing the patch version is thereby removed; `release patch` now
increments the patch version as its name suggests.
`release current` fails if the current version is not a development
version, in which case there is no reserved version to release.
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
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.
* 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 DownloadedDependency abstraction to facilitate handling of downloaded
and hash-verified dependencies.
In CI, missing hashes raise an exception.
For users, unverified downloads log a warning.
* Add DownloadedDependencyHashDatabase which manages a URL-keyed database
of sha256 hashes. It can be conveniently updated incrementally through a
script (src/solidlsp/resources/downloaded_dependency_hashes.json),
provided that language server implementations provide DownloadedDependency
instances statically
Applied in EclipseJDTLS:
* Replace the untyped nested-dict runtime dependency descriptors with DownloadedDependency instances
* Move the JDTLS version-pinning constants into DependencyProvider
* Replace the platform dependency dicts with the VSCodeJavaConfig dataclass
Utils:
* Add FileUtils.ArchiveType to type archive types that were previously plain str
* Add is_running_in_ci() to ls_utils
Updated memory on adding a language server accordingly.
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.
* Apply common functionality already used in JetBrainsFindDeclarationTool
* Remove location-based tool (counteracts Serena's principle to eschew line numbers)
* Apply renaming to use "find declaration" instead of "find defining symbol"
Subprocesses were not cleaned up, in particular.
* Rename SerenaAgent.shutdown to on_shutdown, because it is really a shutdown handler;
it did not perform a shutdown.
* Add new method shutdown which is now called from both sites (and which performs
the full shutdown)
* Manage the process instance used for the dashboard viewer and shut it down
explicitly.
* Initialise all variables used during shutdown immeditaly (avoiding errors when
shutdown is triggered during init)
* Improved symbol retrieval tool responses
1. FindSymbol (LS and JB) now has max_matches. Both tools now perform grouping like other symbol retrieval tools
2. When a result exceeds max_answer_chars, the caller may pass a shortened message. This is used in references and overview tools
* Progressive shortening for tool results that exceed max_answer_chars
Change _limit_length to accept an optional list of closures instead of
a single shortened_result string. Each closure produces a progressively
shorter version of the result, tried in order until one fits. The last
closure is assumed to always fit and is used unconditionally.
* Extended grouper
More flexible grouping logic was needed to achieve the above results. Grouper also no longer
mutates the input (using deepcopy)
---------
Co-authored-by: Dominik Jain <dominik.jain@oraios-ai.de>
Black dependency created a dependabot warning and couldn't be bumped since it collided with pathspec dependencies. Since ruff can also format, black is not needed. But we now have these small format changes, mostly in test files
- Added typed dicts for most responses in jetbrains_types.py
- Fix bug in code_editor (location key no longer existed, found by mypy after adding the types)
Should be merged after next version of plugin release
Added an overview (landing page showing current and available config) and advanced stats windows, controlled by top-right menu
Stats calculation is no longer optional, but we use a trivial token estimator by default