oh-my-agent 15.0.2 → 15.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,129 @@
1
+ """Read-only hook client for the project's already-running Serena daemon."""
2
+
3
+ import argparse
4
+ import asyncio
5
+ import json
6
+ import subprocess
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from mcp import ClientSession
11
+ from mcp.client.streamable_http import streamablehttp_client
12
+
13
+
14
+ def git(root, *args):
15
+ return subprocess.check_output(["git", "-C", str(root), *args])
16
+
17
+
18
+ def check_index(root, package):
19
+ # Whole-project diagnostics are only an index check when that whole scope
20
+ # matches disk. Never stash/rewrite a concurrent worker's files.
21
+ changes = git(root, "diff", "--name-only", "-z", "--", package)
22
+ untracked = git(
23
+ root, "ls-files", "--others", "--exclude-standard", "-z", "--", package
24
+ )
25
+ relevant = [
26
+ p.decode()
27
+ for p in (changes + untracked).split(b"\0")
28
+ if p and (p.endswith((b".dart", b".yaml", b".yml", b".lock")))
29
+ ]
30
+ if relevant:
31
+ raise ValueError(
32
+ "Mobile analysis inputs differ from the index: " + ", ".join(relevant[:10])
33
+ )
34
+ return git(root, "ls-files", "--stage", "-z", "--", package)
35
+
36
+
37
+ def validate_result(result, package, expected_sdk):
38
+ if result.get("complete") is not True or result.get("scope") != package:
39
+ raise ValueError(
40
+ "Serena did not return completed analysis for the requested package"
41
+ )
42
+ if Path(result.get("sdk_executable", "")).resolve() != expected_sdk.resolve():
43
+ raise ValueError(
44
+ "Serena uses a different Dart SDK; restart it with the project SDK"
45
+ )
46
+ diagnostics = result.get("diagnostics")
47
+ if not isinstance(diagnostics, dict):
48
+ raise ValueError("Malformed Serena diagnostics response")
49
+ return diagnostics
50
+
51
+
52
+ async def run(args):
53
+ root = Path(args.root).resolve() if args.root else Path(
54
+ git(Path.cwd(), "rev-parse", "--show-toplevel").decode().strip()
55
+ ).resolve()
56
+ package = str((root / args.project).resolve().relative_to(root))
57
+ sdk = Path(args.dart_sdk).expanduser().resolve(strict=True)
58
+ index = check_index(root, package) if args.staged else None
59
+ url = args.url
60
+ async with streamablehttp_client(
61
+ url, timeout=args.timeout, sse_read_timeout=args.timeout
62
+ ) as (read, write, _):
63
+ async with ClientSession(read, write) as session:
64
+ await session.initialize()
65
+ response = await session.call_tool(
66
+ "get_dart_project_diagnostics",
67
+ {
68
+ "relative_path": package,
69
+ "timeout_seconds": args.timeout,
70
+ },
71
+ )
72
+ if response.isError:
73
+ raise ValueError("Serena analysis failed: " + str(response.content))
74
+ blocks = [
75
+ c.text for c in response.content if getattr(c, "type", "") == "text"
76
+ ]
77
+ if len(blocks) != 1:
78
+ raise ValueError("Unexpected Serena response")
79
+ result = json.loads(blocks[0])
80
+ diagnostics = validate_result(result, package, sdk)
81
+ if result.get("project_root") != str(root):
82
+ raise ValueError("Serena returned diagnostics for a different repository")
83
+ if args.staged and index != check_index(root, package):
84
+ raise ValueError("Git index changed during analysis; retry")
85
+ issues = 0
86
+ for path, entries in diagnostics.items():
87
+ for entry in entries:
88
+ if entry.get("severity", 1) <= 3:
89
+ issues += 1
90
+ line = entry["range"]["start"]["line"] + 1
91
+ print(
92
+ f"{path}:{line}: {entry.get('code', 'diagnostic')}: "
93
+ f"{entry['message']}"
94
+ )
95
+ print(f"Serena Dart: analysis complete, {issues} issue(s); {result['sdk_version']}")
96
+ return 1 if issues else 0
97
+
98
+
99
+ def main():
100
+ parser = argparse.ArgumentParser(description=__doc__)
101
+ parser.add_argument("--project", default="apps/mobile")
102
+ parser.add_argument("--root")
103
+ parser.add_argument("--url", required=True)
104
+ parser.add_argument(
105
+ "--dart-sdk", required=True, help="Project Dart executable (not a mise shim)"
106
+ )
107
+ parser.add_argument("--timeout", type=int, default=60)
108
+ parser.add_argument("--staged", action="store_true")
109
+ args = parser.parse_args()
110
+ if not 1 <= args.timeout <= 120:
111
+ parser.error("--timeout must be between 1 and 120")
112
+ try:
113
+ return asyncio.run(asyncio.wait_for(run(args), timeout=args.timeout))
114
+ except (Exception, KeyboardInterrupt) as error:
115
+
116
+ def details(exc):
117
+ children = getattr(exc, "exceptions", None)
118
+ return (
119
+ "; ".join(details(child) for child in children)
120
+ if children
121
+ else str(exc) or type(exc).__name__
122
+ )
123
+
124
+ print(f"Serena Dart check incomplete: {details(error)}", file=sys.stderr)
125
+ return 2
126
+
127
+
128
+ if __name__ == "__main__":
129
+ sys.exit(main())
@@ -0,0 +1,82 @@
1
+ """Reconcile OMA's adapter with the interpreter that owns the Serena executable."""
2
+
3
+ import argparse
4
+ import hashlib
5
+ import importlib.metadata
6
+ import importlib.util
7
+ import json
8
+ import os
9
+ from pathlib import Path
10
+ import tempfile
11
+
12
+ IMPORT = "from .oma_dart import GetDartProjectDiagnosticsTool\n"
13
+ CACHE_IMPORT = "from .oma_symbol_cache import install_symbol_cache as _install_symbol_cache\n_install_symbol_cache()\n"
14
+ SUPPORTED = {"1.7.0"}
15
+
16
+
17
+ def atomic_write(path, content):
18
+ path.parent.mkdir(parents=True, exist_ok=True)
19
+ fd, temporary = tempfile.mkstemp(dir=path.parent, prefix=".oma-")
20
+ try:
21
+ with os.fdopen(fd, "w") as output:
22
+ output.write(content)
23
+ if path.exists():
24
+ os.chmod(temporary, path.stat().st_mode & 0o777)
25
+ os.replace(temporary, path)
26
+ finally:
27
+ Path(temporary).unlink(missing_ok=True)
28
+
29
+
30
+ def reconcile(package, version, source, check_only=False):
31
+ if version not in SUPPORTED:
32
+ return {
33
+ "status": "unsupported",
34
+ "version": version,
35
+ "error": f"Serena {version} is not supported by this OMA adapter (tested: 1.7.0)",
36
+ }
37
+ payload = (source / "oma_dart.py").read_text()
38
+ cache_payload = (source / "oma_symbol_cache.py").read_text()
39
+ init = package / "tools/__init__.py"
40
+ text = init.read_text()
41
+ updated = text if IMPORT in text else text.rstrip() + "\n" + IMPORT
42
+ if CACHE_IMPORT not in updated:
43
+ updated = updated.rstrip() + "\n" + CACHE_IMPORT
44
+ target = package / "tools/oma_dart.py"
45
+ cache_target = package / "tools/oma_symbol_cache.py"
46
+ changed = (
47
+ updated != text
48
+ or not target.is_file()
49
+ or target.read_text() != payload
50
+ or not cache_target.is_file()
51
+ or cache_target.read_text() != cache_payload
52
+ )
53
+ if changed and not check_only:
54
+ atomic_write(target, payload)
55
+ atomic_write(cache_target, cache_payload)
56
+ if updated != text:
57
+ atomic_write(init, updated)
58
+ revision = hashlib.sha256((version + payload + cache_payload).encode()).hexdigest()
59
+ return {
60
+ "status": "missing" if check_only and changed else "ready",
61
+ "version": version,
62
+ "changed": changed and not check_only,
63
+ "revision": revision,
64
+ "package": str(package),
65
+ }
66
+
67
+
68
+ def main():
69
+ parser = argparse.ArgumentParser(description=__doc__)
70
+ parser.add_argument("--check", action="store_true")
71
+ args = parser.parse_args()
72
+ try:
73
+ version = importlib.metadata.version("serena-agent")
74
+ package = Path(importlib.util.find_spec("serena").origin).parent
75
+ result = reconcile(package, version, Path(__file__).parent, args.check)
76
+ except Exception as error:
77
+ result = {"status": "error", "error": str(error)}
78
+ print(json.dumps(result))
79
+
80
+
81
+ if __name__ == "__main__":
82
+ main()
@@ -0,0 +1,277 @@
1
+ """Dart project diagnostics on Serena's existing language server (Serena 1.7).
2
+
3
+ Installed as serena.tools.oma_dart. No second analysis server is launched.
4
+ """
5
+
6
+ import hashlib
7
+ import json
8
+ import os
9
+ import subprocess
10
+ import threading
11
+ import time
12
+ from contextvars import ContextVar
13
+ from pathlib import Path
14
+ from urllib.parse import unquote, urlparse
15
+
16
+ from serena.tools.tools_base import Tool, ToolMarkerSymbolicRead
17
+ from solidlsp.language_servers.dart_language_server import DartLanguageServer
18
+ from solidlsp.ls_config import LanguageServerId
19
+
20
+
21
+ def snapshot(root):
22
+ """Detect edits during analysis, including new/deleted files and options."""
23
+ result = {}
24
+ for directory, dirs, files in os.walk(root, followlinks=False):
25
+ dirs[:] = [
26
+ d
27
+ for d in dirs
28
+ if d
29
+ not in {
30
+ ".git",
31
+ ".dart_tool",
32
+ "build",
33
+ "node_modules",
34
+ ".venv",
35
+ "android",
36
+ "ios",
37
+ "macos",
38
+ "windows",
39
+ "linux",
40
+ }
41
+ and not Path(directory, d).is_symlink()
42
+ ]
43
+ for name in files:
44
+ path = Path(directory, name)
45
+ if (
46
+ path.suffix in {".dart", ".yaml", ".yml", ".lock"}
47
+ or name == "package_config.json"
48
+ ):
49
+ result[str(path.relative_to(root))] = hashlib.sha256(
50
+ path.read_bytes()
51
+ ).hexdigest()
52
+ package_config = root / ".dart_tool/package_config.json"
53
+ if package_config.is_file():
54
+ result[".dart_tool/package_config.json"] = hashlib.sha256(
55
+ package_config.read_bytes()
56
+ ).hexdigest()
57
+ return result
58
+
59
+
60
+ _original_setup = DartLanguageServer._setup_runtime_dependencies
61
+ _original_init = DartLanguageServer.__init__
62
+ _original_observe = DartLanguageServer._observe_server_notification
63
+ _sdk_root = ContextVar("oma_dart_project_root", default=None)
64
+
65
+
66
+ class AnalysisBarrier:
67
+ """Require a new start/end pair, as the Dart 3.13 reanalyze test does."""
68
+
69
+ def __init__(self):
70
+ self.condition = threading.Condition()
71
+ self.started = 0
72
+ self.completed = 0
73
+ self.busy = False
74
+
75
+ def notify(self, busy):
76
+ with self.condition:
77
+ if busy:
78
+ self.started += 1
79
+ else:
80
+ self.completed = self.started
81
+ self.busy = busy
82
+ self.condition.notify_all()
83
+
84
+ def wait(self, after, timeout):
85
+ with self.condition:
86
+ if not self.condition.wait_for(
87
+ lambda: (
88
+ self.started > after
89
+ and self.completed == self.started
90
+ and not self.busy
91
+ ),
92
+ timeout=timeout,
93
+ ):
94
+ raise TimeoutError("Dart did not report a new completed analysis cycle")
95
+
96
+
97
+ def _init(self, *args, **kwargs):
98
+ self._oma_analysis = AnalysisBarrier()
99
+ root = args[1] if len(args) > 1 else kwargs["repository_root_path"]
100
+ token = _sdk_root.set(root)
101
+ try:
102
+ _original_init(self, *args, **kwargs)
103
+ finally:
104
+ _sdk_root.reset(token)
105
+ command = self._get_process_launch_info().cmd
106
+ self._oma_sdk_executable = command[0] if isinstance(command, list) else None
107
+ self.server.on_notification("$/analyzerStatus", lambda _: None)
108
+
109
+
110
+ def _observe(self, method, params):
111
+ _original_observe(self, method, params)
112
+ if method == "$/analyzerStatus" and isinstance(params, dict):
113
+ busy = params.get("isAnalyzing")
114
+ if isinstance(busy, bool):
115
+ self._oma_analysis.notify(busy)
116
+
117
+
118
+ def _setup(cls, settings):
119
+ executable = settings.get_ls_specific_settings(LanguageServerId.DART).get(
120
+ "dart_executable"
121
+ )
122
+ if not executable:
123
+ return _original_setup(settings)
124
+ if executable == "mise":
125
+ try:
126
+ flutter = subprocess.check_output(
127
+ ["mise", "which", "flutter"], cwd=_sdk_root.get(),
128
+ text=True, stderr=subprocess.PIPE, timeout=5,
129
+ ).strip()
130
+ executable = str(Path(flutter).parent / "cache/dart-sdk/bin/dart")
131
+ if not Path(executable).is_file():
132
+ raise FileNotFoundError(executable)
133
+ except (subprocess.SubprocessError, FileNotFoundError):
134
+ executable = subprocess.check_output(
135
+ ["mise", "which", "dart"], cwd=_sdk_root.get(),
136
+ text=True, timeout=5,
137
+ ).strip()
138
+ path = Path(executable).expanduser().resolve(strict=True)
139
+ if not path.is_file() or not os.access(path, os.X_OK):
140
+ raise ValueError(f"Not an executable Dart SDK: {path}")
141
+ # An argv list also supports SDK paths containing spaces.
142
+ return [str(path), "language-server", "--client-id", "oma.serena"]
143
+
144
+
145
+ DartLanguageServer._setup_runtime_dependencies = classmethod(_setup)
146
+ DartLanguageServer.__init__ = _init
147
+ DartLanguageServer._observe_server_notification = _observe
148
+
149
+
150
+ def refresh(ls, timeout):
151
+ barrier = ls._oma_analysis
152
+ baseline = barrier.started
153
+ started_at = time.monotonic()
154
+ ls.server.send_request("dart/reanalyze")
155
+ barrier.wait(baseline, max(0, timeout - (time.monotonic() - started_at)))
156
+
157
+
158
+ def file_diagnostics(
159
+ self, relative_file_path, start_line=0, end_line=-1, min_severity=4
160
+ ):
161
+ uri = self._validate_text_document_diagnostics_request(
162
+ relative_file_path,
163
+ start_line,
164
+ end_line,
165
+ min_severity,
166
+ )
167
+ with self.open_file(relative_file_path):
168
+ refresh(self, 60)
169
+ diagnostics = self._get_cached_published_diagnostics(uri) or []
170
+ return self._filter_diagnostics(diagnostics, start_line, end_line, min_severity)
171
+
172
+
173
+ DartLanguageServer.request_text_document_diagnostics = file_diagnostics
174
+
175
+
176
+ def collect(ls, package, repository, timeout):
177
+ """A missing completion method/timeout is an error, never empty diagnostics."""
178
+ before = snapshot(package)
179
+ candidates = sorted(name for name in before if name.endswith(".dart"))
180
+ if not candidates:
181
+ raise ValueError("No Dart source files in the requested package")
182
+ entry = (
183
+ "lib/main.dart"
184
+ if "lib/main.dart" in candidates
185
+ else next(
186
+ (name for name in candidates if name.startswith("lib/")), candidates[0]
187
+ )
188
+ )
189
+ anchor = str((package / entry).relative_to(repository))
190
+ # Keep one real file open per package, preserving its analysis context
191
+ # between hook calls. open_file refreshes an existing buffer from disk.
192
+ anchors = getattr(ls, "_oma_dart_anchors", {})
193
+ for name in list(anchors):
194
+ if not (repository / name).is_file():
195
+ anchors.pop(name).__exit__(None, None, None)
196
+ if anchor not in anchors:
197
+ context = ls.open_file(anchor)
198
+ context.__enter__()
199
+ anchors[anchor] = context
200
+ ls._oma_dart_anchors = anchors
201
+ old_timeout = ls.server._request_timeout
202
+ try:
203
+ ls.server._request_timeout = timeout
204
+ with ls.open_file(anchor):
205
+ # The server watches disk asynchronously; explicitly refresh before
206
+ # asking for its completion barrier, including package/config edits.
207
+ # Dart 3.13.4 does not yet implement workspace/analysis/complete.
208
+ # Do not mistake an old idle event for completion of this refresh.
209
+ refresh(ls, timeout)
210
+ diagnostics = {}
211
+ with ls._published_diagnostics_condition:
212
+ for uri, entries in ls._published_diagnostics.items():
213
+ path = Path(unquote(urlparse(uri).path))
214
+ if path.is_relative_to(package) and path.is_file() and entries:
215
+ diagnostics[str(path.relative_to(repository))] = entries.copy()
216
+ finally:
217
+ ls.server._request_timeout = old_timeout
218
+ if before != snapshot(package):
219
+ raise RuntimeError(
220
+ "Project changed during analysis; retry on a stable working tree"
221
+ )
222
+ return diagnostics
223
+
224
+
225
+ class GetDartProjectDiagnosticsTool(Tool, ToolMarkerSymbolicRead):
226
+ """Wait for Dart project analysis and return all published diagnostics."""
227
+
228
+ def apply(self, relative_path: str, timeout_seconds: int = 60) -> str:
229
+ """Analyze a pub package using the existing Dart LSP.
230
+
231
+ :param relative_path: package directory containing pubspec.yaml.
232
+ :param timeout_seconds: maximum wait per analysis request (1..120).
233
+ :return: complete diagnostics, SDK identity, and package scope.
234
+ """
235
+ if not 1 <= timeout_seconds <= 120:
236
+ raise ValueError("timeout_seconds must be between 1 and 120")
237
+ repository = Path(self.project.project_root).resolve()
238
+ package = (repository / relative_path).resolve()
239
+ if (
240
+ not package.is_relative_to(repository)
241
+ or not (package / "pubspec.yaml").is_file()
242
+ ):
243
+ raise ValueError("Expected a pub package inside the active project")
244
+ self.project.ls_sync_file_system_changes()
245
+ sources = sorted(package.glob("lib/**/*.dart")) or sorted(
246
+ package.glob("*.dart")
247
+ )
248
+ if not sources:
249
+ raise ValueError("Package has no Dart entry file")
250
+ ls = self.create_language_server_symbol_retriever().get_language_server(
251
+ str(sources[0].relative_to(repository))
252
+ )
253
+ if not isinstance(ls, DartLanguageServer):
254
+ raise ValueError("Requested file is not managed by Dart LSP")
255
+ executable = ls.custom_settings.get("dart_executable")
256
+ if not executable:
257
+ raise ValueError(
258
+ "Set ls_specific_settings.dart.dart_executable to the project SDK, "
259
+ "then restart Serena"
260
+ )
261
+ if executable == "mise":
262
+ # Query the actual launched argv, not the possibly changed PATH.
263
+ executable = ls._oma_sdk_executable
264
+ version = subprocess.check_output(
265
+ [executable, "--version"], text=True, timeout=5
266
+ ).strip()
267
+ diagnostics = collect(ls, package, repository, timeout_seconds)
268
+ return json.dumps(
269
+ {
270
+ "complete": True,
271
+ "project_root": str(repository),
272
+ "scope": str(package.relative_to(repository)),
273
+ "sdk_executable": str(Path(executable).resolve()),
274
+ "sdk_version": version,
275
+ "diagnostics": diagnostics,
276
+ }
277
+ )
@@ -0,0 +1,133 @@
1
+ """File-granular symbol caching for Serena 1.7.0's long-lived Python process.
2
+
3
+ Legacy pickles eagerly restore every file and retain mutable workspace trees.
4
+ SQLite keeps cold files on disk; serialized entries isolate request mutations.
5
+ """
6
+
7
+ import hashlib
8
+ import logging
9
+ import pickle
10
+ import sqlite3
11
+ import threading
12
+ from collections.abc import MutableMapping
13
+
14
+ log = logging.getLogger(__name__)
15
+ FORMAT_VERSION = 1
16
+ MAX_ENTRY_BYTES = 16 * 1024 * 1024
17
+
18
+
19
+ class DiskSymbolCache(MutableMapping):
20
+ def __init__(self, directory, kind, version):
21
+ self._lock = threading.RLock()
22
+ self._connection = None
23
+ digest = hashlib.sha256(pickle.dumps((FORMAT_VERSION, version))).hexdigest()[
24
+ :24
25
+ ]
26
+ self.path = directory / f"oma-{kind}-{digest}.sqlite3"
27
+ try:
28
+ self._connection = sqlite3.connect(
29
+ self.path, timeout=2, isolation_level=None, check_same_thread=False
30
+ )
31
+ self._connection.execute("PRAGMA journal_mode=WAL")
32
+ self._connection.execute("PRAGMA cache_size=-2048")
33
+ self._connection.execute("PRAGMA mmap_size=0")
34
+ self._connection.execute(
35
+ "CREATE TABLE IF NOT EXISTS symbols (path TEXT PRIMARY KEY, value BLOB NOT NULL)"
36
+ )
37
+ except (OSError, sqlite3.Error) as error:
38
+ self._disable(error)
39
+
40
+ def _disable(self, error):
41
+ # A disposable cache must never make code intelligence unavailable.
42
+ log.warning(
43
+ "OMA symbol cache unavailable at %s; using uncached requests: %s",
44
+ self.path,
45
+ error,
46
+ )
47
+ self.close()
48
+
49
+ def _query(self, sql, params=()):
50
+ with self._lock:
51
+ if self._connection is None:
52
+ return []
53
+ try:
54
+ return self._connection.execute(sql, params).fetchall()
55
+ except sqlite3.Error as error:
56
+ self._disable(error)
57
+ return []
58
+
59
+ def __getitem__(self, key):
60
+ rows = self._query("SELECT value FROM symbols WHERE path=?", (key,))
61
+ if not rows:
62
+ raise KeyError(key)
63
+ try:
64
+ # Each request owns its decoded graph. Attaching a parent package
65
+ # in request_full_symbol_tree cannot mutate the persistent cache.
66
+ return pickle.loads(rows[0][0])
67
+ except Exception:
68
+ self._query("DELETE FROM symbols WHERE path=?", (key,))
69
+ raise KeyError(key) from None
70
+
71
+ def __setitem__(self, key, value):
72
+ with self._lock:
73
+ if self._connection is None:
74
+ return
75
+ try:
76
+ payload = pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL)
77
+ except Exception:
78
+ return
79
+ if len(payload) > MAX_ENTRY_BYTES:
80
+ self._query("DELETE FROM symbols WHERE path=?", (key,))
81
+ return
82
+ self._query("INSERT OR REPLACE INTO symbols VALUES (?, ?)", (key, payload))
83
+
84
+ def __delitem__(self, key):
85
+ with self._lock:
86
+ if not self._query("SELECT 1 FROM symbols WHERE path=?", (key,)):
87
+ raise KeyError(key)
88
+ self._query("DELETE FROM symbols WHERE path=?", (key,))
89
+
90
+ def __iter__(self):
91
+ return iter(row[0] for row in self._query("SELECT path FROM symbols"))
92
+
93
+ def __len__(self):
94
+ rows = self._query("SELECT count(*) FROM symbols")
95
+ return rows[0][0] if rows else 0
96
+
97
+ def close(self):
98
+ with self._lock:
99
+ if self._connection is not None:
100
+ self._connection.close()
101
+ self._connection = None
102
+
103
+ def __del__(self):
104
+ self.close()
105
+
106
+
107
+ def _load_raw(self):
108
+ self._raw_document_symbols_cache = DiskSymbolCache(
109
+ self.cache_dir, "raw-symbols", self._raw_document_symbols_cache_version()
110
+ )
111
+
112
+
113
+ def _load_documents(self):
114
+ self._document_symbols_cache = DiskSymbolCache(
115
+ self.cache_dir, "document-symbols", self._document_symbols_cache_version()
116
+ )
117
+
118
+
119
+ def _save_raw(self):
120
+ self._raw_document_symbols_cache_is_modified = False
121
+
122
+
123
+ def _save_documents(self):
124
+ self._document_symbols_cache_is_modified = False
125
+
126
+
127
+ def install_symbol_cache():
128
+ from solidlsp.ls import SolidLanguageServer
129
+
130
+ SolidLanguageServer._load_raw_document_symbols_cache = _load_raw
131
+ SolidLanguageServer._load_document_symbols_cache = _load_documents
132
+ SolidLanguageServer._save_raw_document_symbols_cache = _save_raw
133
+ SolidLanguageServer._save_document_symbols_cache = _save_documents