python-delphi-lsp 2.0.1__py3-none-any.whl → 2.0.3__py3-none-any.whl
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.
- delphi_lsp/_version.py +1 -1
- delphi_lsp/agent_cli.py +12 -5
- delphi_lsp/agent_context.py +163 -62
- delphi_lsp/agent_templates.py +92 -64
- delphi_lsp/agent_workspace.py +7 -2
- {python_delphi_lsp-2.0.1.dist-info → python_delphi_lsp-2.0.3.dist-info}/METADATA +46 -31
- {python_delphi_lsp-2.0.1.dist-info → python_delphi_lsp-2.0.3.dist-info}/RECORD +11 -11
- {python_delphi_lsp-2.0.1.dist-info → python_delphi_lsp-2.0.3.dist-info}/WHEEL +0 -0
- {python_delphi_lsp-2.0.1.dist-info → python_delphi_lsp-2.0.3.dist-info}/entry_points.txt +0 -0
- {python_delphi_lsp-2.0.1.dist-info → python_delphi_lsp-2.0.3.dist-info}/licenses/LICENSE +0 -0
- {python_delphi_lsp-2.0.1.dist-info → python_delphi_lsp-2.0.3.dist-info}/top_level.txt +0 -0
delphi_lsp/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2.0.
|
|
1
|
+
__version__ = "2.0.3"
|
delphi_lsp/agent_cli.py
CHANGED
|
@@ -67,11 +67,19 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
67
67
|
|
|
68
68
|
opencode = subcommands.add_parser("opencode", help="Install opencode integration.")
|
|
69
69
|
opencode_commands = opencode.add_subparsers(dest="opencode_command", required=True)
|
|
70
|
-
opencode_install = opencode_commands.add_parser(
|
|
70
|
+
opencode_install = opencode_commands.add_parser(
|
|
71
|
+
"install", help="Install the package-named skill, Markdown agent, and OpenCode plugin."
|
|
72
|
+
)
|
|
71
73
|
opencode_install.add_argument("--target", type=Path, default=Path("."))
|
|
72
74
|
opencode_install.add_argument("--python", default=sys.executable)
|
|
73
75
|
opencode_install.add_argument("--force", action="store_true")
|
|
74
|
-
opencode_install.add_argument(
|
|
76
|
+
opencode_install.add_argument(
|
|
77
|
+
"--write-agent",
|
|
78
|
+
"--write-config",
|
|
79
|
+
dest="write_config",
|
|
80
|
+
action="store_true",
|
|
81
|
+
help="Deprecated compatibility option; the Markdown agent is always installed and opencode.json is never touched.",
|
|
82
|
+
)
|
|
75
83
|
opencode_install.set_defaults(func=_opencode_install)
|
|
76
84
|
|
|
77
85
|
worker = subcommands.add_parser("worker", help="Serve Protocol v2 NDJSON requests.")
|
|
@@ -139,7 +147,7 @@ def _skill_install(args: argparse.Namespace) -> None:
|
|
|
139
147
|
|
|
140
148
|
|
|
141
149
|
def _opencode_install(args: argparse.Namespace) -> None:
|
|
142
|
-
skill_path, plugin_path,
|
|
150
|
+
skill_path, plugin_path, agent_path = install_opencode_support(
|
|
143
151
|
args.target,
|
|
144
152
|
python_executable=args.python,
|
|
145
153
|
force=args.force,
|
|
@@ -147,8 +155,7 @@ def _opencode_install(args: argparse.Namespace) -> None:
|
|
|
147
155
|
)
|
|
148
156
|
print(skill_path)
|
|
149
157
|
print(plugin_path)
|
|
150
|
-
|
|
151
|
-
print(config_path)
|
|
158
|
+
print(agent_path)
|
|
152
159
|
|
|
153
160
|
|
|
154
161
|
def _worker(args: argparse.Namespace) -> None:
|
delphi_lsp/agent_context.py
CHANGED
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from bisect import bisect_left, bisect_right
|
|
4
4
|
from collections.abc import Mapping
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
+
from heapq import heappop, heappush
|
|
6
7
|
import hashlib
|
|
7
8
|
import json
|
|
8
9
|
from pathlib import Path, PureWindowsPath
|
|
@@ -14,13 +15,12 @@ from .agent_protocol import (
|
|
|
14
15
|
AgentResponse,
|
|
15
16
|
ContextBudget,
|
|
16
17
|
Focus,
|
|
17
|
-
Page,
|
|
18
18
|
make_target_id,
|
|
19
19
|
paginate_items,
|
|
20
20
|
)
|
|
21
21
|
from .agent_metrics import build_workspace_metrics, project_metric_item, unit_metric_item
|
|
22
22
|
from .agent_relations import ProjectRelationIndex, RelationTarget
|
|
23
|
-
from .agent_workspace import AgentWorkspace, unit_display_path, unit_source_path, unit_target_id
|
|
23
|
+
from .agent_workspace import AgentUnit, AgentWorkspace, unit_display_path, unit_source_path, unit_target_id
|
|
24
24
|
from .consts import AttributeName, SyntaxNodeType
|
|
25
25
|
from .lsp_server import build_outline_semantic_model, multiline_string_block_end
|
|
26
26
|
from .nodes import CompoundSyntaxNode, SyntaxNode
|
|
@@ -121,6 +121,10 @@ class _SourceDocument:
|
|
|
121
121
|
self.tokens = tuple(_lex_delphi(text))
|
|
122
122
|
self.token_starts = tuple(token.start for token in self.tokens)
|
|
123
123
|
self.directive_starts = tuple(token.start for token in self.tokens if token.directive)
|
|
124
|
+
self.declaration_section_indexes = [0]
|
|
125
|
+
self.declaration_section_checkpoints: dict[int, tuple[str, int, int, int]] = {
|
|
126
|
+
0: ("", 0, 0, 0)
|
|
127
|
+
}
|
|
124
128
|
words = [token for token in self.tokens if token.word and not token.escaped]
|
|
125
129
|
self.unit_kind = next(
|
|
126
130
|
(token.value for token in words if token.value in {"unit", "program", "library", "package"}),
|
|
@@ -129,6 +133,7 @@ class _SourceDocument:
|
|
|
129
133
|
implementation = next((token for token in words if token.value == "implementation"), None)
|
|
130
134
|
self.implementation_line = self.line_col(implementation.start)[0] if implementation else 0
|
|
131
135
|
self.routine_spans: dict[int, tuple[int, int] | None] = {}
|
|
136
|
+
self.routine_token_spans: dict[int, tuple[int, int, int] | None] = {}
|
|
132
137
|
self.parser_spans: dict[str, tuple[int, int] | None] = {}
|
|
133
138
|
self._full_parse_attempted = False
|
|
134
139
|
self._full_parse_result: object | None = None
|
|
@@ -230,6 +235,7 @@ class _Registry:
|
|
|
230
235
|
entries: tuple[_SymbolEntry, ...]
|
|
231
236
|
by_target: dict[str, _SymbolEntry]
|
|
232
237
|
sources: dict[Path, _SourceDocument]
|
|
238
|
+
ranked_queries: dict[str, tuple[_SymbolEntry, ...]]
|
|
233
239
|
|
|
234
240
|
|
|
235
241
|
class AgentContext:
|
|
@@ -280,7 +286,12 @@ class AgentContext:
|
|
|
280
286
|
return self._handle_focus(parsed, revision)
|
|
281
287
|
if parsed.action == "find":
|
|
282
288
|
registry = self._require_registry(revision)
|
|
283
|
-
|
|
289
|
+
ranked = registry.ranked_queries.get(parsed.query)
|
|
290
|
+
if ranked is None:
|
|
291
|
+
ranked = tuple(_ranked_entries(registry.entries, parsed.query))
|
|
292
|
+
registry.ranked_queries.clear()
|
|
293
|
+
registry.ranked_queries[parsed.query] = ranked
|
|
294
|
+
items = [entry.card() for entry in ranked]
|
|
284
295
|
return self._response(parsed, revision, items)
|
|
285
296
|
if parsed.action == "inspect":
|
|
286
297
|
registry = self._require_registry(revision)
|
|
@@ -293,9 +304,10 @@ class AgentContext:
|
|
|
293
304
|
previous_project_id = self._workspace.active_project_id
|
|
294
305
|
selected_project_id = requested_project_id or previous_project_id
|
|
295
306
|
if selected_project_id:
|
|
296
|
-
self._workspace.
|
|
307
|
+
revision = self._workspace._select_project_with_revision(selected_project_id)
|
|
308
|
+
else:
|
|
309
|
+
revision = self._workspace.workspace_revision
|
|
297
310
|
current_project_id = self._workspace.active_project_id
|
|
298
|
-
revision = self._workspace.workspace_revision
|
|
299
311
|
|
|
300
312
|
if current_project_id != previous_project_id:
|
|
301
313
|
self._registry = None
|
|
@@ -782,6 +794,7 @@ def _build_registry(workspace: AgentWorkspace, project_id: str, revision: str) -
|
|
|
782
794
|
entries=entries_tuple,
|
|
783
795
|
by_target={entry.target_id: entry for entry in entries_tuple},
|
|
784
796
|
sources=sources,
|
|
797
|
+
ranked_queries={},
|
|
785
798
|
)
|
|
786
799
|
|
|
787
800
|
|
|
@@ -925,32 +938,47 @@ def _correct_outline_symbol_kind(document: _SourceDocument, symbol: Symbol) -> N
|
|
|
925
938
|
symbol.kind = SymbolKind.TYPE
|
|
926
939
|
|
|
927
940
|
|
|
941
|
+
def _advance_declaration_section(
|
|
942
|
+
state: tuple[str, int, int, int],
|
|
943
|
+
token: _Token,
|
|
944
|
+
) -> tuple[str, int, int, int]:
|
|
945
|
+
section, parentheses, brackets, angles = state
|
|
946
|
+
if token.directive:
|
|
947
|
+
return state
|
|
948
|
+
if token.value == "(":
|
|
949
|
+
parentheses += 1
|
|
950
|
+
elif token.value == ")":
|
|
951
|
+
parentheses = max(0, parentheses - 1)
|
|
952
|
+
elif token.value == "[":
|
|
953
|
+
brackets += 1
|
|
954
|
+
elif token.value == "]":
|
|
955
|
+
brackets = max(0, brackets - 1)
|
|
956
|
+
elif token.value == "<":
|
|
957
|
+
angles += 1
|
|
958
|
+
elif token.value == ">":
|
|
959
|
+
angles = max(0, angles - 1)
|
|
960
|
+
elif not parentheses and not brackets and not angles and token.word:
|
|
961
|
+
if token.value in {"const", "resourcestring", "threadvar", "type", "var"}:
|
|
962
|
+
section = token.value
|
|
963
|
+
elif token.value in {"implementation", "initialization", "finalization"}:
|
|
964
|
+
section = ""
|
|
965
|
+
return section, parentheses, brackets, angles
|
|
966
|
+
|
|
967
|
+
|
|
928
968
|
def _declaration_section(document: _SourceDocument, offset: int) -> str:
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
elif token.value == "]":
|
|
943
|
-
brackets = max(0, brackets - 1)
|
|
944
|
-
elif token.value == "<":
|
|
945
|
-
angles += 1
|
|
946
|
-
elif token.value == ">":
|
|
947
|
-
angles = max(0, angles - 1)
|
|
948
|
-
elif not any((parentheses, brackets, angles)) and token.word:
|
|
949
|
-
if token.value in {"const", "resourcestring", "threadvar", "type", "var"}:
|
|
950
|
-
section = token.value
|
|
951
|
-
elif token.value in {"implementation", "initialization", "finalization"}:
|
|
952
|
-
section = ""
|
|
953
|
-
return section
|
|
969
|
+
target_index = document.first_token_index(offset)
|
|
970
|
+
cached = document.declaration_section_checkpoints.get(target_index)
|
|
971
|
+
if cached is not None:
|
|
972
|
+
return cached[0]
|
|
973
|
+
checkpoint_position = bisect_right(document.declaration_section_indexes, target_index) - 1
|
|
974
|
+
checkpoint_index = document.declaration_section_indexes[checkpoint_position]
|
|
975
|
+
state = document.declaration_section_checkpoints[checkpoint_index]
|
|
976
|
+
for token in document.tokens[checkpoint_index:target_index]:
|
|
977
|
+
state = _advance_declaration_section(state, token)
|
|
978
|
+
insert_at = bisect_left(document.declaration_section_indexes, target_index)
|
|
979
|
+
document.declaration_section_indexes.insert(insert_at, target_index)
|
|
980
|
+
document.declaration_section_checkpoints[target_index] = state
|
|
981
|
+
return state[0]
|
|
954
982
|
|
|
955
983
|
|
|
956
984
|
def _declared_symbol_name(document: _SourceDocument, symbol: Symbol) -> str:
|
|
@@ -1208,20 +1236,47 @@ def _exclude_routine_locals(
|
|
|
1208
1236
|
span = _raw_routine_span(raw, document)
|
|
1209
1237
|
if span is not None:
|
|
1210
1238
|
containers.append((span[0], span[1], raw))
|
|
1239
|
+
if not containers:
|
|
1240
|
+
return symbols
|
|
1211
1241
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1242
|
+
containers.sort(key=lambda item: (item[0], item[1]))
|
|
1243
|
+
positioned = sorted(
|
|
1244
|
+
(
|
|
1245
|
+
document.offset(
|
|
1246
|
+
raw.symbol.decl_range.start_line,
|
|
1247
|
+
raw.symbol.decl_range.start_col,
|
|
1248
|
+
),
|
|
1249
|
+
order,
|
|
1250
|
+
raw,
|
|
1217
1251
|
)
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1252
|
+
for order, raw in enumerate(symbols)
|
|
1253
|
+
)
|
|
1254
|
+
active_ends: list[tuple[int, int, int]] = []
|
|
1255
|
+
active_ids: set[int] = set()
|
|
1256
|
+
excluded_orders: set[int] = set()
|
|
1257
|
+
container_index = 0
|
|
1258
|
+
for offset, order, raw in positioned:
|
|
1259
|
+
while (
|
|
1260
|
+
container_index < len(containers)
|
|
1261
|
+
and containers[container_index][0] < offset
|
|
1221
1262
|
):
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1263
|
+
_, end, container = containers[container_index]
|
|
1264
|
+
container_id = id(container)
|
|
1265
|
+
heappush(active_ends, (end, container_index, container_id))
|
|
1266
|
+
active_ids.add(container_id)
|
|
1267
|
+
container_index += 1
|
|
1268
|
+
while active_ends and active_ends[0][0] <= offset:
|
|
1269
|
+
_, _, container_id = heappop(active_ends)
|
|
1270
|
+
active_ids.discard(container_id)
|
|
1271
|
+
raw_id = id(raw)
|
|
1272
|
+
if active_ids and (raw_id not in active_ids or len(active_ids) > 1):
|
|
1273
|
+
excluded_orders.add(order)
|
|
1274
|
+
|
|
1275
|
+
return [
|
|
1276
|
+
raw
|
|
1277
|
+
for order, raw in enumerate(symbols)
|
|
1278
|
+
if order not in excluded_orders
|
|
1279
|
+
]
|
|
1225
1280
|
|
|
1226
1281
|
|
|
1227
1282
|
def _raw_routine_span(
|
|
@@ -1523,6 +1578,7 @@ def _routine_span(
|
|
|
1523
1578
|
document.tokens,
|
|
1524
1579
|
document.token_starts,
|
|
1525
1580
|
token_index,
|
|
1581
|
+
cache=document.routine_token_spans,
|
|
1526
1582
|
)
|
|
1527
1583
|
span = (start, found[1]) if found is not None else None
|
|
1528
1584
|
document.routine_spans[start] = span
|
|
@@ -1534,6 +1590,7 @@ def _find_routine_token_span(
|
|
|
1534
1590
|
token_starts: tuple[int, ...],
|
|
1535
1591
|
start_index: int,
|
|
1536
1592
|
*,
|
|
1593
|
+
cache: dict[int, tuple[int, int, int] | None] | None = None,
|
|
1537
1594
|
depth: int = 0,
|
|
1538
1595
|
) -> tuple[int, int, int] | None:
|
|
1539
1596
|
if depth > 64:
|
|
@@ -1541,56 +1598,100 @@ def _find_routine_token_span(
|
|
|
1541
1598
|
routine_index = _routine_keyword_index(tokens, start_index)
|
|
1542
1599
|
if routine_index is None:
|
|
1543
1600
|
return None
|
|
1601
|
+
spans = cache if cache is not None else {}
|
|
1602
|
+
missing = object()
|
|
1603
|
+
cached = spans.get(routine_index, missing)
|
|
1604
|
+
if cached is not missing:
|
|
1605
|
+
if cached is None:
|
|
1606
|
+
return None
|
|
1607
|
+
return tokens[start_index].start, cached[1], cached[2]
|
|
1608
|
+
|
|
1544
1609
|
heading_end = _heading_semicolon_index(tokens, routine_index)
|
|
1545
1610
|
if heading_end is None:
|
|
1611
|
+
spans[routine_index] = None
|
|
1546
1612
|
return None
|
|
1547
1613
|
|
|
1548
|
-
|
|
1549
|
-
|
|
1614
|
+
frames: list[list[int]] = [[routine_index, heading_end + 1]]
|
|
1615
|
+
|
|
1616
|
+
def reject_active_frames() -> None:
|
|
1617
|
+
for active_routine_index, _ in frames:
|
|
1618
|
+
spans[active_routine_index] = None
|
|
1619
|
+
frames.clear()
|
|
1620
|
+
|
|
1621
|
+
while frames:
|
|
1622
|
+
frame = frames[-1]
|
|
1623
|
+
frame_routine_index, index = frame
|
|
1624
|
+
if index >= len(tokens):
|
|
1625
|
+
reject_active_frames()
|
|
1626
|
+
continue
|
|
1627
|
+
|
|
1550
1628
|
token = tokens[index]
|
|
1551
1629
|
if token.directive:
|
|
1552
|
-
|
|
1630
|
+
reject_active_frames()
|
|
1631
|
+
continue
|
|
1553
1632
|
if token.word and not token.escaped:
|
|
1554
1633
|
if token.value in _NO_BODY_DIRECTIVES:
|
|
1555
|
-
|
|
1634
|
+
spans[frame_routine_index] = None
|
|
1635
|
+
frames.pop()
|
|
1636
|
+
continue
|
|
1556
1637
|
if token.value in {"implementation", "initialization", "finalization"}:
|
|
1557
|
-
|
|
1638
|
+
reject_active_frames()
|
|
1639
|
+
continue
|
|
1558
1640
|
if (
|
|
1559
1641
|
token.value in _STRUCTURED_TYPE_WORDS
|
|
1560
1642
|
and _is_structured_type_opener(tokens, index)
|
|
1561
1643
|
):
|
|
1562
1644
|
if index + 1 < len(tokens) and tokens[index + 1].value == ";":
|
|
1563
|
-
index
|
|
1645
|
+
frame[1] = index + 2
|
|
1564
1646
|
continue
|
|
1565
1647
|
structured_end = _match_end_terminated_block(tokens, index)
|
|
1566
1648
|
if structured_end is None:
|
|
1567
|
-
|
|
1568
|
-
|
|
1649
|
+
reject_active_frames()
|
|
1650
|
+
continue
|
|
1651
|
+
frame[1] = bisect_left(token_starts, structured_end)
|
|
1569
1652
|
continue
|
|
1570
1653
|
if token.value == "end":
|
|
1571
|
-
|
|
1654
|
+
reject_active_frames()
|
|
1655
|
+
continue
|
|
1572
1656
|
if token.value in {"begin", "asm"}:
|
|
1573
1657
|
end = _match_end_terminated_block(tokens, index)
|
|
1574
1658
|
if end is None:
|
|
1575
|
-
|
|
1659
|
+
reject_active_frames()
|
|
1660
|
+
continue
|
|
1576
1661
|
end_index = bisect_left(token_starts, end)
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
token_starts,
|
|
1582
|
-
index,
|
|
1583
|
-
depth=depth + 1,
|
|
1662
|
+
spans[frame_routine_index] = (
|
|
1663
|
+
tokens[frame_routine_index].start,
|
|
1664
|
+
end,
|
|
1665
|
+
end_index,
|
|
1584
1666
|
)
|
|
1667
|
+
frames.pop()
|
|
1668
|
+
continue
|
|
1669
|
+
if token.value in _ROUTINE_WORDS and _is_nested_routine_declaration(tokens, index):
|
|
1670
|
+
nested_routine_index = _routine_keyword_index(tokens, index)
|
|
1671
|
+
if nested_routine_index is None:
|
|
1672
|
+
frame[1] = index + 1
|
|
1673
|
+
continue
|
|
1674
|
+
nested = spans.get(nested_routine_index, missing)
|
|
1675
|
+
if nested is missing:
|
|
1676
|
+
nested_heading_end = _heading_semicolon_index(tokens, nested_routine_index)
|
|
1677
|
+
if nested_heading_end is None:
|
|
1678
|
+
spans[nested_routine_index] = None
|
|
1679
|
+
continue
|
|
1680
|
+
frames.append([nested_routine_index, nested_heading_end + 1])
|
|
1681
|
+
continue
|
|
1585
1682
|
if nested is not None:
|
|
1586
|
-
|
|
1683
|
+
frame[1] = max(index + 1, nested[2])
|
|
1587
1684
|
continue
|
|
1588
1685
|
skipped = _routine_declaration_end_index(tokens, index)
|
|
1589
1686
|
if skipped is not None:
|
|
1590
|
-
|
|
1687
|
+
frame[1] = skipped + 1
|
|
1591
1688
|
continue
|
|
1592
|
-
index
|
|
1593
|
-
|
|
1689
|
+
frame[1] = index + 1
|
|
1690
|
+
|
|
1691
|
+
result = spans.get(routine_index)
|
|
1692
|
+
if result is None:
|
|
1693
|
+
return None
|
|
1694
|
+
return tokens[start_index].start, result[1], result[2]
|
|
1594
1695
|
|
|
1595
1696
|
|
|
1596
1697
|
def _routine_keyword_index(
|
delphi_lsp/agent_templates.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import hashlib
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
import json
|
|
5
6
|
import os
|
|
@@ -7,14 +8,20 @@ import secrets
|
|
|
7
8
|
import stat
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
SKILL_NAME = "delphi-
|
|
11
|
+
SKILL_NAME = "python-delphi-lsp"
|
|
12
|
+
AGENT_NAME = "python-delphi-lsp"
|
|
13
|
+
_LEGACY_SKILL_NAME = "delphi-codebase-navigator"
|
|
14
|
+
_LEGACY_SKILL_RELATIVE_PATH = Path(".agents") / "skills" / _LEGACY_SKILL_NAME / "SKILL.md"
|
|
15
|
+
_LEGACY_SKILL_SHA256 = "0843d37bd48431c2b992f191b985175a3c60fe5b56d28e0a46b5e18db6383b28"
|
|
11
16
|
_LEGACY_TOOL_RELATIVE_PATH = Path(".opencode") / "tools" / "delphi_codebase.ts"
|
|
12
17
|
|
|
13
18
|
|
|
14
19
|
def install_skill(target: str | Path, *, force: bool = False) -> Path:
|
|
15
20
|
target_path = Path(target).expanduser().resolve()
|
|
21
|
+
legacy_path = _preflight_legacy_skill(target_path, force=force)
|
|
16
22
|
skill_path = target_path / ".agents" / "skills" / SKILL_NAME / "SKILL.md"
|
|
17
23
|
_write_text(skill_path, _skill_markdown(), force=force)
|
|
24
|
+
_remove_legacy_skill(legacy_path)
|
|
18
25
|
return skill_path
|
|
19
26
|
|
|
20
27
|
|
|
@@ -24,26 +31,30 @@ def install_opencode_support(
|
|
|
24
31
|
python_executable: str,
|
|
25
32
|
force: bool = False,
|
|
26
33
|
write_config: bool = False,
|
|
27
|
-
) -> tuple[Path, Path, Path
|
|
34
|
+
) -> tuple[Path, Path, Path]:
|
|
28
35
|
target_path = Path(target).expanduser().resolve()
|
|
29
36
|
legacy_path = target_path / _LEGACY_TOOL_RELATIVE_PATH
|
|
37
|
+
legacy_skill_path = _preflight_legacy_skill(target_path, force=force)
|
|
30
38
|
skill_path = target_path / ".agents" / "skills" / SKILL_NAME / "SKILL.md"
|
|
31
39
|
plugin_path = target_path / ".opencode" / "plugins" / "delphi_codebase.ts"
|
|
32
|
-
|
|
40
|
+
agent_path = target_path / ".opencode" / "agents" / f"{AGENT_NAME}.md"
|
|
33
41
|
skill_text = _skill_markdown()
|
|
34
42
|
plugin_text = _opencode_plugin(python_executable)
|
|
43
|
+
agent_text = _agent_markdown()
|
|
35
44
|
|
|
36
45
|
legacy_before = _preflight_legacy(legacy_path, force=force)
|
|
37
46
|
snapshots: dict[Path, bytes | None] = {
|
|
38
47
|
legacy_path: legacy_before,
|
|
48
|
+
**(
|
|
49
|
+
{legacy_skill_path: legacy_skill_path.read_bytes()}
|
|
50
|
+
if legacy_skill_path is not None
|
|
51
|
+
else {}
|
|
52
|
+
),
|
|
39
53
|
skill_path: _preflight_destination(skill_path, skill_text, force=force),
|
|
40
54
|
plugin_path: _preflight_destination(plugin_path, plugin_text, force=force),
|
|
55
|
+
agent_path: _preflight_destination(agent_path, agent_text, force=force),
|
|
41
56
|
}
|
|
42
|
-
writes = [(skill_path, skill_text), (plugin_path, plugin_text)]
|
|
43
|
-
if config_path is not None:
|
|
44
|
-
config_before, config_text = _render_opencode_config(config_path)
|
|
45
|
-
snapshots[config_path] = config_before
|
|
46
|
-
writes.append((config_path, config_text))
|
|
57
|
+
writes = [(skill_path, skill_text), (plugin_path, plugin_text), (agent_path, agent_text)]
|
|
47
58
|
|
|
48
59
|
try:
|
|
49
60
|
for path, text in writes:
|
|
@@ -51,11 +62,39 @@ def install_opencode_support(
|
|
|
51
62
|
_write_text(path, text, force=True)
|
|
52
63
|
if legacy_before is not None:
|
|
53
64
|
legacy_path.unlink()
|
|
65
|
+
_remove_legacy_skill(legacy_skill_path)
|
|
54
66
|
except BaseException:
|
|
55
67
|
for path, content in reversed(tuple(snapshots.items())):
|
|
56
68
|
_restore_file(path, content)
|
|
57
69
|
raise
|
|
58
|
-
|
|
70
|
+
_ = write_config # Deprecated compatibility input; user configuration is never touched.
|
|
71
|
+
return skill_path, plugin_path, agent_path
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _preflight_legacy_skill(target: Path, *, force: bool) -> Path | None:
|
|
75
|
+
legacy_path = target / _LEGACY_SKILL_RELATIVE_PATH
|
|
76
|
+
legacy_path.relative_to(target)
|
|
77
|
+
_reject_symbolic_link(legacy_path)
|
|
78
|
+
if not legacy_path.exists():
|
|
79
|
+
return None
|
|
80
|
+
if not legacy_path.is_file():
|
|
81
|
+
raise FileExistsError(f"Legacy skill path is not a file: {legacy_path}")
|
|
82
|
+
digest = hashlib.sha256(legacy_path.read_bytes()).hexdigest()
|
|
83
|
+
if digest != _LEGACY_SKILL_SHA256 and not force:
|
|
84
|
+
raise FileExistsError(
|
|
85
|
+
f"Refusing to remove modified legacy skill without force: {legacy_path}"
|
|
86
|
+
)
|
|
87
|
+
return legacy_path
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _remove_legacy_skill(path: Path | None) -> None:
|
|
91
|
+
if path is None:
|
|
92
|
+
return
|
|
93
|
+
path.unlink()
|
|
94
|
+
try:
|
|
95
|
+
path.parent.rmdir()
|
|
96
|
+
except OSError:
|
|
97
|
+
pass
|
|
59
98
|
|
|
60
99
|
|
|
61
100
|
def _preflight_legacy(legacy_path: Path, *, force: bool) -> bytes | None:
|
|
@@ -157,13 +196,18 @@ def _write_bytes_atomic(path: Path, content: bytes) -> None:
|
|
|
157
196
|
|
|
158
197
|
|
|
159
198
|
def _reject_symbolic_link(path: Path) -> None:
|
|
160
|
-
|
|
161
|
-
|
|
199
|
+
current = path
|
|
200
|
+
while current != current.parent:
|
|
201
|
+
if current.is_symlink():
|
|
202
|
+
raise FileExistsError(
|
|
203
|
+
f"Generated destination must not contain a symbolic link: {current}"
|
|
204
|
+
)
|
|
205
|
+
current = current.parent
|
|
162
206
|
|
|
163
207
|
|
|
164
208
|
def _skill_markdown() -> str:
|
|
165
209
|
return """---
|
|
166
|
-
name: delphi-
|
|
210
|
+
name: python-delphi-lsp
|
|
167
211
|
description: Inspect Delphi and Object Pascal codebases through the Protocol v2 semantic navigator.
|
|
168
212
|
compatibility: opencode
|
|
169
213
|
metadata:
|
|
@@ -192,6 +236,42 @@ Prefer `summary` and `declaration`, narrow `max_items` and `max_chars`, and requ
|
|
|
192
236
|
"""
|
|
193
237
|
|
|
194
238
|
|
|
239
|
+
def _agent_markdown() -> str:
|
|
240
|
+
return """---
|
|
241
|
+
description: Inspect Delphi and Object Pascal codebases through python-delphi-lsp.
|
|
242
|
+
mode: all
|
|
243
|
+
temperature: 0
|
|
244
|
+
permission:
|
|
245
|
+
delphi_codebase: allow
|
|
246
|
+
skill:
|
|
247
|
+
"*": deny
|
|
248
|
+
python-delphi-lsp: allow
|
|
249
|
+
lsp: deny
|
|
250
|
+
bash: deny
|
|
251
|
+
read: deny
|
|
252
|
+
glob: deny
|
|
253
|
+
grep: deny
|
|
254
|
+
list: deny
|
|
255
|
+
edit: deny
|
|
256
|
+
write: deny
|
|
257
|
+
patch: deny
|
|
258
|
+
task: deny
|
|
259
|
+
webfetch: deny
|
|
260
|
+
websearch: deny
|
|
261
|
+
question: deny
|
|
262
|
+
todowrite: deny
|
|
263
|
+
todoread: deny
|
|
264
|
+
codebase_map: deny
|
|
265
|
+
code_guidelines: deny
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
Load `python-delphi-lsp` first, then use only `delphi_codebase` for Delphi and
|
|
269
|
+
Object Pascal codebase inspection. Do not use `lsp`, `bash`, `read`, `glob`,
|
|
270
|
+
`grep`, `edit`, `write`, `task`, `webfetch`, or `todowrite`. Preserve returned
|
|
271
|
+
citations exactly and report partial or ambiguous semantic evidence explicitly.
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
|
|
195
275
|
def _opencode_plugin(python_executable: str) -> str:
|
|
196
276
|
python_json = json.dumps(python_executable)
|
|
197
277
|
template = '''import { tool, type Plugin } from "@opencode-ai/plugin"
|
|
@@ -525,56 +605,4 @@ export const DelphiCodebasePlugin: Plugin = async (_input) => {
|
|
|
525
605
|
return template.replace("__PYTHON_EXECUTABLE__", python_json)
|
|
526
606
|
|
|
527
607
|
|
|
528
|
-
def _render_opencode_config(config_path: Path) -> tuple[bytes | None, str]:
|
|
529
|
-
_reject_symbolic_link(config_path)
|
|
530
|
-
if config_path.exists():
|
|
531
|
-
if not config_path.is_file():
|
|
532
|
-
raise ValueError(f"opencode config path is not a file: {config_path}")
|
|
533
|
-
config_before = config_path.read_bytes()
|
|
534
|
-
config = json.loads(config_before.decode("utf-8"))
|
|
535
|
-
else:
|
|
536
|
-
config_before = None
|
|
537
|
-
config = {"$schema": "https://opencode.ai/config.json"}
|
|
538
|
-
if not isinstance(config, dict):
|
|
539
|
-
raise ValueError("opencode config must be a JSON object")
|
|
540
|
-
if "agent" not in config:
|
|
541
|
-
agents = {}
|
|
542
|
-
config["agent"] = agents
|
|
543
|
-
else:
|
|
544
|
-
agents = config["agent"]
|
|
545
|
-
if not isinstance(agents, dict):
|
|
546
|
-
raise ValueError("opencode config field 'agent' must be a JSON object")
|
|
547
|
-
agents["vllm-delphi-codebase"] = {
|
|
548
|
-
"description": "Use the Delphi codebase navigator skill and plugin without direct filesystem source inspection.",
|
|
549
|
-
"temperature": 0,
|
|
550
|
-
"prompt": (
|
|
551
|
-
"load delphi-codebase-navigator first, then use only delphi_codebase for Delphi/Object Pascal "
|
|
552
|
-
"codebase inspection. Do not use lsp, bash, read, glob, grep, edit, write, task, webfetch, or todowrite."
|
|
553
|
-
),
|
|
554
|
-
"tools": {
|
|
555
|
-
"delphi_codebase": True,
|
|
556
|
-
"skill": True,
|
|
557
|
-
"lsp": False,
|
|
558
|
-
"bash": False,
|
|
559
|
-
"read": False,
|
|
560
|
-
"glob": False,
|
|
561
|
-
"grep": False,
|
|
562
|
-
"edit": False,
|
|
563
|
-
"write": False,
|
|
564
|
-
"task": False,
|
|
565
|
-
"webfetch": False,
|
|
566
|
-
"todowrite": False,
|
|
567
|
-
},
|
|
568
|
-
"permission": {
|
|
569
|
-
"delphi_codebase": "allow",
|
|
570
|
-
"skill": {
|
|
571
|
-
"*": "deny",
|
|
572
|
-
SKILL_NAME: "allow",
|
|
573
|
-
},
|
|
574
|
-
"lsp": "deny",
|
|
575
|
-
},
|
|
576
|
-
}
|
|
577
|
-
return config_before, json.dumps(config, indent=2, sort_keys=True) + "\n"
|
|
578
|
-
|
|
579
|
-
|
|
580
608
|
__all__ = ["SKILL_NAME", "install_opencode_support", "install_skill"]
|
delphi_lsp/agent_workspace.py
CHANGED
|
@@ -264,6 +264,9 @@ class AgentWorkspace:
|
|
|
264
264
|
return f"workspace_v2_{fingerprint}"
|
|
265
265
|
|
|
266
266
|
def select_project(self, project_id: str) -> None:
|
|
267
|
+
self._select_project_with_revision(project_id)
|
|
268
|
+
|
|
269
|
+
def _select_project_with_revision(self, project_id: str) -> str:
|
|
267
270
|
if project_id not in self._project_paths:
|
|
268
271
|
raise AgentProtocolError("project_not_found", f"Project not found: {project_id}.")
|
|
269
272
|
project_path = self._project_paths[project_id]
|
|
@@ -280,7 +283,7 @@ class AgentWorkspace:
|
|
|
280
283
|
fingerprint = _selection_fingerprint(discovery, cached.result, root=self._root)
|
|
281
284
|
if fingerprint == cached.fingerprint:
|
|
282
285
|
self._activate_project(project_id, discovery, cached.result)
|
|
283
|
-
return
|
|
286
|
+
return f"workspace_v2_{fingerprint}"
|
|
284
287
|
|
|
285
288
|
if project_path is None:
|
|
286
289
|
result = _catalog_workspace_sources(discovery)
|
|
@@ -292,11 +295,13 @@ class AgentWorkspace:
|
|
|
292
295
|
source_transform=_outline_agent_source,
|
|
293
296
|
)
|
|
294
297
|
result = indexer.index(str(project_path))
|
|
298
|
+
fingerprint = _selection_fingerprint(discovery, result, root=self._root)
|
|
295
299
|
self._project_cache[project_id] = _ProjectCache(
|
|
296
300
|
result=result,
|
|
297
|
-
fingerprint=
|
|
301
|
+
fingerprint=fingerprint,
|
|
298
302
|
)
|
|
299
303
|
self._activate_project(project_id, discovery, result)
|
|
304
|
+
return f"workspace_v2_{fingerprint}"
|
|
300
305
|
|
|
301
306
|
def _activate_project(
|
|
302
307
|
self,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-delphi-lsp
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.3
|
|
4
4
|
Summary: Python Delphi/Object Pascal parser, semantic indexer, and language server.
|
|
5
5
|
Author: Dark Light
|
|
6
6
|
License-Expression: MPL-2.0
|
|
@@ -41,7 +41,7 @@ Dynamic: license-file
|
|
|
41
41
|
|
|
42
42
|
`python-delphi-lsp` parses Delphi/Object Pascal, builds semantic and project
|
|
43
43
|
indexes, serves LSP, and provides bounded codebase navigation for agents.
|
|
44
|
-
Version 2.0.
|
|
44
|
+
Version 2.0.3 is authored by Dark Light and supports Windows, macOS, and Linux.
|
|
45
45
|
|
|
46
46
|
## Install and quick start
|
|
47
47
|
|
|
@@ -177,7 +177,7 @@ delphi-lsp-agent view --root PATH [--project-file FILE] --layer LAYER
|
|
|
177
177
|
delphi-lsp-agent index --root PATH [--project-file FILE] [--out FILE]
|
|
178
178
|
delphi-lsp-agent skill install [--target PATH] [--force]
|
|
179
179
|
delphi-lsp-agent opencode install [--target PATH] [--python PYTHON]
|
|
180
|
-
[--force] [--write-config]
|
|
180
|
+
[--force] [--write-agent|--write-config]
|
|
181
181
|
delphi-lsp-agent worker --root PATH [--project-file FILE]
|
|
182
182
|
```
|
|
183
183
|
|
|
@@ -187,8 +187,9 @@ delphi-lsp-agent worker --root PATH [--project-file FILE]
|
|
|
187
187
|
returns a project summary and detailed unit metric objects; `--query` filters
|
|
188
188
|
units by name or path.
|
|
189
189
|
`index` materializes overview, projects, and problems JSON. `skill install`
|
|
190
|
-
writes the skill; `opencode install` writes
|
|
191
|
-
|
|
190
|
+
writes the skill; `opencode install` writes the package-named skill, Markdown
|
|
191
|
+
agent, and plugin. The two deprecated write flags are harmless aliases and do
|
|
192
|
+
not change user configuration.
|
|
192
193
|
`worker` serves NDJSON over standard input/output.
|
|
193
194
|
|
|
194
195
|
Protocol v2 actions are `open`, `find`, `inspect`, `trace`, `focus`,
|
|
@@ -225,49 +226,63 @@ optimization does not remove LSP functionality.
|
|
|
225
226
|
Install the generated integration in a worktree:
|
|
226
227
|
|
|
227
228
|
```bash
|
|
228
|
-
delphi-lsp-agent opencode install --target .
|
|
229
|
+
delphi-lsp-agent opencode install --target .
|
|
229
230
|
```
|
|
230
231
|
|
|
231
232
|
It writes:
|
|
232
233
|
|
|
233
234
|
```text
|
|
234
|
-
.agents/skills/delphi-
|
|
235
|
+
.agents/skills/python-delphi-lsp/SKILL.md
|
|
235
236
|
.opencode/plugins/delphi_codebase.ts
|
|
237
|
+
.opencode/agents/python-delphi-lsp.md
|
|
236
238
|
```
|
|
237
239
|
|
|
238
|
-
The
|
|
239
|
-
`delphi-
|
|
240
|
+
The package-named Markdown agent enables only the
|
|
241
|
+
`python-delphi-lsp` skill and `delphi_codebase`. It denies
|
|
240
242
|
`bash`, `read`, `glob`, `grep`, and `lsp`, along with edit/write and
|
|
241
243
|
other raw source tools. The skill is enabled. The installer does not use the
|
|
242
|
-
retired `.opencode/tools` path.
|
|
244
|
+
retired `.opencode/tools` path and never reads or changes `opencode.json`; that
|
|
245
|
+
file remains entirely user-owned. The deprecated `--write-config` and
|
|
246
|
+
`--write-agent` options are accepted harmlessly for compatibility.
|
|
243
247
|
|
|
244
248
|
The plugin maintains one worker per session/root, reusing focus and indexes.
|
|
245
249
|
During compaction it restores the focus and summary into the new context.
|
|
246
250
|
Transport failure, session deletion, and plugin disposal clean up the worker.
|
|
247
251
|
|
|
248
|
-
A generated OpenCode agent
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
252
|
+
A generated OpenCode agent starts with this Markdown frontmatter:
|
|
253
|
+
|
|
254
|
+
```markdown
|
|
255
|
+
---
|
|
256
|
+
description: Inspect Delphi and Object Pascal codebases through python-delphi-lsp.
|
|
257
|
+
mode: all
|
|
258
|
+
temperature: 0
|
|
259
|
+
permission:
|
|
260
|
+
delphi_codebase: allow
|
|
261
|
+
skill:
|
|
262
|
+
"*": deny
|
|
263
|
+
python-delphi-lsp: allow
|
|
264
|
+
lsp: deny
|
|
265
|
+
bash: deny
|
|
266
|
+
read: deny
|
|
267
|
+
glob: deny
|
|
268
|
+
grep: deny
|
|
269
|
+
list: deny
|
|
270
|
+
edit: deny
|
|
271
|
+
write: deny
|
|
272
|
+
patch: deny
|
|
273
|
+
task: deny
|
|
274
|
+
webfetch: deny
|
|
275
|
+
websearch: deny
|
|
276
|
+
question: deny
|
|
277
|
+
todowrite: deny
|
|
278
|
+
todoread: deny
|
|
279
|
+
codebase_map: deny
|
|
280
|
+
code_guidelines: deny
|
|
281
|
+
---
|
|
267
282
|
```
|
|
268
283
|
|
|
269
|
-
Select `
|
|
270
|
-
|
|
284
|
+
Select `python-delphi-lsp`, ask it to load the `python-delphi-lsp` skill, then
|
|
285
|
+
use `delphi_codebase` actions such as
|
|
271
286
|
`open`, `find`, `focus`, and `inspect`. Use semantic tool calls, not raw
|
|
272
287
|
source tools.
|
|
273
288
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
delphi_lsp/__init__.py,sha256=Gtoeg_D3hLGCFi34wdYiKrIByAbkWe1zS84v6_KJ6_4,3768
|
|
2
|
-
delphi_lsp/_version.py,sha256=
|
|
3
|
-
delphi_lsp/agent_cli.py,sha256=
|
|
4
|
-
delphi_lsp/agent_context.py,sha256=
|
|
2
|
+
delphi_lsp/_version.py,sha256=_GEKEa6BYjBV34SZkSlAR87aCM5Y9G0aSI0LXL52iJg,22
|
|
3
|
+
delphi_lsp/agent_cli.py,sha256=Mma4C1Ca_Hd_9dl0mePT7uIypw6j6gDWdKu9XxIMXQQ,9226
|
|
4
|
+
delphi_lsp/agent_context.py,sha256=FdRQ2MFGDcqBDGMnD0w2X8p1kB3YwyTPDm_4cdCmwbA,77504
|
|
5
5
|
delphi_lsp/agent_layers.py,sha256=7TmplTL7m_riS8BxZCD-3j1p8iOGiD9QWHay5ZcDYiw,21369
|
|
6
6
|
delphi_lsp/agent_metrics.py,sha256=6y9DrSnKitZbwRFQrofanw6k89YGqbvEacqgSKHgh2c,2695
|
|
7
7
|
delphi_lsp/agent_protocol.py,sha256=cZ1LIMhrkOb5CqiGcjSH1eZUNqtJCYySUc54h-PCyOs,12551
|
|
8
8
|
delphi_lsp/agent_relations.py,sha256=pXhuCjfwmicG3VG7lYtGvKKv-mFPQ_hJ3w0eavqMoV0,32900
|
|
9
|
-
delphi_lsp/agent_templates.py,sha256=
|
|
10
|
-
delphi_lsp/agent_workspace.py,sha256=
|
|
9
|
+
delphi_lsp/agent_templates.py,sha256=LXtbAPj0Nxvp3ycZARPELEb51BFoi2vtiCe-pYHNteE,21834
|
|
10
|
+
delphi_lsp/agent_workspace.py,sha256=HvCWfMVv08Jlx257my4hkkXCLUNoPcy5AAnOnl_mNUU,23761
|
|
11
11
|
delphi_lsp/binary.py,sha256=40mEpE0SBYmifwEoNHwSU7IU_D2usVyWYWHJ73aDfoo,8529
|
|
12
12
|
delphi_lsp/comment_builder.py,sha256=XvFW7WTbYy7yGN3MG40gtabTvhGj1ea1MYkySDbVMDA,832
|
|
13
13
|
delphi_lsp/consts.py,sha256=qM40iwxXmvEileWHJVLMYlQSkLoLyQaqM-Zscz3VJbs,5958
|
|
@@ -26,9 +26,9 @@ delphi_lsp/semantic_builder.py,sha256=ulA7xHJpltjc6cTo5wh9yTcYazuIykM-YLMXD1h-wz
|
|
|
26
26
|
delphi_lsp/source_reader.py,sha256=HWiw25sLVZ6s1GGpMl17uZ-o68HoxTEd3Z0__m1vsSE,520
|
|
27
27
|
delphi_lsp/workspace.py,sha256=TU__SujItRlW8hB2kuYC7ZJx1WQVOj7frYxvo7rDqeI,2137
|
|
28
28
|
delphi_lsp/writer.py,sha256=j-cnyiHFNOTFECL7Ehm-m43ye7ev7qFeCCscFMjrAOY,2503
|
|
29
|
-
python_delphi_lsp-2.0.
|
|
30
|
-
python_delphi_lsp-2.0.
|
|
31
|
-
python_delphi_lsp-2.0.
|
|
32
|
-
python_delphi_lsp-2.0.
|
|
33
|
-
python_delphi_lsp-2.0.
|
|
34
|
-
python_delphi_lsp-2.0.
|
|
29
|
+
python_delphi_lsp-2.0.3.dist-info/licenses/LICENSE,sha256=-rPda9qyJvHAhjCx3ZF-Efy07F4eAg4sFvg6ChOGPoU,16726
|
|
30
|
+
python_delphi_lsp-2.0.3.dist-info/METADATA,sha256=Dw8iQ0nH5GwHL2eIbYPEMXpVcoMp0Mc0tkGltO7iUMo,14942
|
|
31
|
+
python_delphi_lsp-2.0.3.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
32
|
+
python_delphi_lsp-2.0.3.dist-info/entry_points.txt,sha256=rh_yGH7diowge_acQcST87A-ewH1QezEsAYC8T5ineY,103
|
|
33
|
+
python_delphi_lsp-2.0.3.dist-info/top_level.txt,sha256=0Zp0sjw5Qtic3EQX9zNeegURga8NuXk6c2e9PN-3OTs,11
|
|
34
|
+
python_delphi_lsp-2.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|