Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,10 @@ jobs:
- name: Set up Julia
uses: julia-actions/setup-julia@v2
with:
version: '1.10'
version: '1.12.6'
- name: Install Julia LanguageServer
shell: bash
run: julia -e 'using Pkg; Pkg.add("LanguageServer")'
run: julia -e 'using Pkg; Pkg.add(Pkg.PackageSpec(name="LanguageServer", version="5.0.0"))'
- name: Setup Haskell toolchain
if: runner.os != 'Windows'
uses: haskell/ghcup-setup@v1
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ downloads/
eggs/
.eggs/
lib/
!test/resources/repos/dart/test_repo/lib/
!test/resources/repos/dart/test_repo/lib/diagnostics_sample.dart
lib64/
parts/
sdist/
Expand Down Expand Up @@ -271,4 +273,4 @@ cabal.project.local*
zz-misc/
vue-implementation/

news/news.json
news/news.json
1 change: 1 addition & 0 deletions .serena/project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ project_name: "serena"
languages:
- python
- typescript
- clojure

# whether to use project's .gitignore files to ignore files
ignore_all_files_in_gitignore: true
Expand Down
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,21 @@ Status of the `main` branch. Changes prior to the next official version change w
See updated [documentation on modes](https://oraios.github.io/serena/02-usage/050_configuration.html#modes).
- Serena's default configuration now uses `interactive` and `editing` as `base_modes` instead of as `default_modes`.

* JetBrains:
- Add new tools:
- `jet_brains_list_inspections`: Lists available IDE inspections (akin to diagnostics), optionally filtered by language or group
- `jet_brains_run_inspections`: Runs IDE inspections on a file and returns the results

* LSP Tools:
- Add new tools:
- `find_declaration`: Finds the declaration/definition of a symbol
- `find_implementations`: Finds the implementations of an interface or abstract method
- `get_diagnostics_for_file`: Retrieves diagnostics for a specific file (errors, warnings, etc.)
- `get_diagnostics_for_symbol`: Retrieves diagnostics pertaining to a specific symbol

* Language Servers:
- Java (`eclipse.jdt.ls`): Add upstream JDTLS mode for offline / restricted-network use. Setting both `jdtls_path` and `lombok_path` in `ls_specific_settings.java` makes Serena use an existing upstream JDTLS installation (e.g. `brew install jdtls`) and the system JDK 21+, skipping the ~500 MB vscode-java VSIX, Gradle, and IntelliCode downloads. New related setting `java_home` lets the user override the JDK used to launch JDTLS. Default behavior unchanged — the JDTLS workspace hash is preserved bit-for-bit for users on the default route, so existing project caches are reused without a one-time reindex; the launcher path is mixed into the hash only when `jdtls_path` is set, isolating upstream installations from the default workspace. #1415


# v1.2.0 (2026-04-27)

* General:
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,10 @@ without reading entire files.
| find referencing symbols | yes | yes |
| search in project dependencies | -- | yes |
| type hierarchy | -- | yes |
| find declaration | -- | yes |
| find implementations | -- | yes |
| find declaration | yes | yes |
| find implementations | yes | yes |
| query external projects | yes | yes |
| diagnostics/inspections | yes | yes |

### Refactoring

Expand Down
176 changes: 176 additions & 0 deletions scripts/demo_diagnostics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
"""
Demonstrates diagnostics tools and edit-tool diagnostic reporting on the Serena repo itself.

The script creates a temporary Python file inside this repository, introduces one warning,
shows file and symbol diagnostics, then introduces another warning and verifies that the
second edit reports only the newly introduced warning.
"""

import json
import shutil
import tempfile
from pathlib import Path
from pprint import pprint

from serena.agent import SerenaAgent
from serena.config.serena_config import LanguageBackend, ProjectConfig, RegisteredProject, SerenaConfig
from serena.constants import REPO_ROOT
from serena.project import Project
from serena.tools import (
CreateTextFileTool,
EditingToolWithDiagnostics,
GetDiagnosticsForFileTool,
GetDiagnosticsForSymbolTool,
ReplaceContentTool,
)
from solidlsp.ls_config import Language

SEPARATOR = "=" * 80
REPO_PATH = Path(REPO_ROOT)
EDIT_RESULT_PREFIX = "Edit introduced new warning-or-higher diagnostics: "


def make_agent() -> SerenaAgent:
"""Create an LSP-backed Serena agent for the Serena repository."""
serena_config = SerenaConfig.from_config_file()
serena_config.web_dashboard = False
serena_config.language_backend = LanguageBackend.LSP

project = Project(
project_root=str(REPO_PATH),
project_config=ProjectConfig(
project_name="demo_serena_repo",
languages=[Language.PYTHON],
ignored_paths=[],
excluded_tools=[],
read_only=False,
ignore_all_files_in_gitignore=True,
initial_prompt="",
encoding="utf-8",
),
serena_config=serena_config,
)
serena_config.projects = [RegisteredProject.from_project_instance(project)]
return SerenaAgent(project="demo_serena_repo", serena_config=serena_config)


def print_section(title: str) -> None:
"""Print a visibly separated section header."""
print(f"\n{SEPARATOR}")
print(title)
print(SEPARATOR)


def parse_json_result(result: str) -> object:
"""Parse and pretty-print JSON tool output."""
parsed = json.loads(result)
pprint(parsed, width=200)
return parsed


def parse_edit_diagnostics_result(result: str) -> dict:
"""Extract the grouped diagnostics payload from an edit-tool result."""
assert result.startswith(EDIT_RESULT_PREFIX), result
return json.loads(result[len(EDIT_RESULT_PREFIX) :])


if __name__ == "__main__":
EditingToolWithDiagnostics.ENABLE_DIAGNOSTICS = True

temp_dir = Path(tempfile.mkdtemp(prefix="serena_demo_", dir=REPO_PATH))
temp_file = temp_dir / "demo_temp_diagnostics.py"
relative_path = temp_file.relative_to(REPO_PATH).as_posix()

initial_content = """def demo_existing_issue() -> int:
value = 1
return value
"""

agent = make_agent()

try:
# letting the language server finish startup
agent.execute_task(lambda: None)

create_text_file_tool = agent.get_tool(CreateTextFileTool)
replace_content_tool = agent.get_tool(ReplaceContentTool)
get_diagnostics_for_file_tool = agent.get_tool(GetDiagnosticsForFileTool)
get_diagnostics_for_symbol_tool = agent.get_tool(GetDiagnosticsForSymbolTool)

# creating a clean temporary file
print_section("Create Temporary File")
create_result = agent.execute_task(lambda: create_text_file_tool.apply(relative_path=relative_path, content=initial_content))
print(create_result)

# showing file diagnostics before introducing any warning
print_section("Initial File Diagnostics")
initial_diagnostics_result = agent.execute_task(
lambda: get_diagnostics_for_file_tool.apply(relative_path=relative_path, min_severity=2)
)
initial_diagnostics = parse_json_result(initial_diagnostics_result)
assert initial_diagnostics == {}, initial_diagnostics

# introducing the first warning
print_section("First Edit Result")
first_edit_result = agent.execute_task(
lambda: replace_content_tool.apply(
relative_path=relative_path,
needle="value = 1",
repl="value = missing_one",
mode="literal",
)
)
print(first_edit_result)
first_edit_diagnostics = parse_edit_diagnostics_result(first_edit_result)
pprint(first_edit_diagnostics, width=200)
assert "missing_one" in json.dumps(first_edit_diagnostics), first_edit_diagnostics

# showing the file- and symbol-level diagnostics after the first warning
print_section("File Diagnostics After First Edit")
diagnostics_after_first_edit_result = agent.execute_task(
lambda: get_diagnostics_for_file_tool.apply(relative_path=relative_path, min_severity=2)
)
diagnostics_after_first_edit = parse_json_result(diagnostics_after_first_edit_result)
assert "missing_one" in json.dumps(diagnostics_after_first_edit), diagnostics_after_first_edit

print_section("Symbol Diagnostics After First Edit")
symbol_diagnostics_result = agent.execute_task(
lambda: get_diagnostics_for_symbol_tool.apply(
name_path="demo_existing_issue",
reference_file=relative_path,
min_severity=2,
)
)
symbol_diagnostics = parse_json_result(symbol_diagnostics_result)
assert "missing_one" in json.dumps(symbol_diagnostics), symbol_diagnostics

# introducing a second warning while keeping the first one unchanged
print_section("Second Edit Result")
second_edit_result = agent.execute_task(
lambda: replace_content_tool.apply(
relative_path=relative_path,
needle=" return value\n",
repl=" other = missing_two\n return value + other\n",
mode="literal",
)
)
print(second_edit_result)
second_edit_diagnostics = parse_edit_diagnostics_result(second_edit_result)
pprint(second_edit_diagnostics, width=200)
second_edit_json = json.dumps(second_edit_diagnostics)
assert "missing_two" in second_edit_json, second_edit_diagnostics
assert "missing_one" not in second_edit_json, second_edit_diagnostics
print("\nVerified: the second edit result reports only the newly introduced warning.")

# showing the complete file diagnostics after both warnings exist
print_section("File Diagnostics After Second Edit")
diagnostics_after_second_edit_result = agent.execute_task(
lambda: get_diagnostics_for_file_tool.apply(relative_path=relative_path, min_severity=2)
)
diagnostics_after_second_edit = parse_json_result(diagnostics_after_second_edit_result)
diagnostics_after_second_edit_json = json.dumps(diagnostics_after_second_edit)
assert "missing_one" in diagnostics_after_second_edit_json, diagnostics_after_second_edit
assert "missing_two" in diagnostics_after_second_edit_json, diagnostics_after_second_edit
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
agent.shutdown()
112 changes: 112 additions & 0 deletions scripts/demo_find_defining_symbol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"""
Demonstrates both defining-symbol tools on the Python test repository.
"""

import json
import re
from pathlib import Path
from pprint import pprint

from serena.agent import SerenaAgent
from serena.config.serena_config import LanguageBackend, ProjectConfig, RegisteredProject, SerenaConfig
from serena.constants import REPO_ROOT
from serena.project import Project
from serena.tools import FindDeclarationTool
from solidlsp.ls_config import Language

SEPARATOR = "=" * 80
PYTHON_TEST_REPO = Path(REPO_ROOT) / "test" / "resources" / "repos" / "python" / "test_repo"
SERVICES_FILE = Path("test_repo") / "services.py"


def make_agent(project_root: Path, language: Language, project_name: str) -> SerenaAgent:
"""Create an LSP-backed Serena agent for a single explicit project."""
serena_config = SerenaConfig.from_config_file()
serena_config.web_dashboard = False
serena_config.language_backend = LanguageBackend.LSP

project = Project(
project_root=str(project_root),
project_config=ProjectConfig(
project_name=project_name,
languages=[language],
ignored_paths=[],
excluded_tools=[],
read_only=False,
ignore_all_files_in_gitignore=True,
initial_prompt="",
encoding="utf-8",
),
serena_config=serena_config,
)
serena_config.projects = [RegisteredProject.from_project_instance(project)]
return SerenaAgent(project=project_name, serena_config=serena_config)


def print_section(title: str) -> None:
"""Print a visibly separated section header."""
print(f"\n{SEPARATOR}")
print(title)
print(SEPARATOR)


def find_identifier_occurrence_position(file_path: Path, identifier: str, occurrence_index: int = 0) -> tuple[int, int]:
"""Find the 0-based position of an identifier occurrence in a file."""
pattern = re.compile(r"\b" + re.escape(identifier) + r"\b")
current_occurrence_index = 0
with file_path.open(encoding="utf-8") as f:
for line_index, line in enumerate(f):
for match in pattern.finditer(line):
if current_occurrence_index == occurrence_index:
return line_index, match.start()
current_occurrence_index += 1
raise ValueError(f"Could not find occurrence {occurrence_index} of {identifier!r} in {file_path}")


if __name__ == "__main__":
agent = make_agent(PYTHON_TEST_REPO, Language.PYTHON, "demo_python_test_repo")

try:
# letting the language server finish startup
agent.execute_task(lambda: None)

relative_path = SERVICES_FILE.as_posix()
services_abs_path = PYTHON_TEST_REPO / SERVICES_FILE

# resolving via regex over the full file
find_by_regex_tool = agent.get_tool(FindDeclarationTool)
regex_result = agent.execute_task(
lambda: find_by_regex_tool.apply(
regex=r"from \.models import Item, (User)",
relative_path=relative_path,
include_info=True,
)
)

print_section("FindDefiningSymbolTool (File Regex)")
regex_symbol = json.loads(regex_result)
pprint(regex_symbol, width=200)

# resolving via regex restricted to one containing symbol body
contained_regex_result = agent.execute_task(
lambda: find_by_regex_tool.apply(
regex=r"=\s+(User)\(",
relative_path=relative_path,
containing_symbol_name_path="UserService/create_user",
include_info=True,
)
)

print_section("FindDefiningSymbolTool (Contained Regex)")
contained_regex_symbol = json.loads(contained_regex_result)
pprint(contained_regex_symbol, width=200)

# validating the demonstrated result
for symbol in [regex_symbol, contained_regex_symbol]:
assert symbol is not None, "Expected a defining symbol result"
assert symbol.get("relative_path") is not None
assert "models.py" in symbol["relative_path"], symbol
assert "User" in json.dumps(symbol), symbol
print("\nVerified definition target: User in models.py")
finally:
agent.shutdown()
Loading
Loading