python-delphi-lsp 1.1.1__py3-none-any.whl → 2.0.1__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.
Files changed (41) hide show
  1. {delphiast → delphi_lsp}/__init__.py +22 -0
  2. delphi_lsp/_version.py +1 -0
  3. delphi_lsp/agent_cli.py +243 -0
  4. delphi_lsp/agent_context.py +2138 -0
  5. {delphiast → delphi_lsp}/agent_layers.py +67 -2
  6. delphi_lsp/agent_metrics.py +82 -0
  7. delphi_lsp/agent_protocol.py +391 -0
  8. delphi_lsp/agent_relations.py +859 -0
  9. delphi_lsp/agent_templates.py +580 -0
  10. delphi_lsp/agent_workspace.py +695 -0
  11. {delphiast → delphi_lsp}/lark_builder.py +14 -2
  12. {delphiast → delphi_lsp}/lsp_server.py +270 -48
  13. delphi_lsp/metrics.py +759 -0
  14. {delphiast → delphi_lsp}/project_discovery.py +183 -40
  15. {delphiast → delphi_lsp}/project_indexer.py +6 -0
  16. delphi_lsp/source_reader.py +17 -0
  17. {delphiast → delphi_lsp}/workspace.py +15 -1
  18. python_delphi_lsp-2.0.1.dist-info/METADATA +380 -0
  19. python_delphi_lsp-2.0.1.dist-info/RECORD +34 -0
  20. python_delphi_lsp-2.0.1.dist-info/entry_points.txt +3 -0
  21. python_delphi_lsp-2.0.1.dist-info/top_level.txt +1 -0
  22. delphiast/agent_cli.py +0 -113
  23. delphiast/agent_templates.py +0 -180
  24. delphiast/source_reader.py +0 -15
  25. python_delphi_lsp-1.1.1.dist-info/METADATA +0 -434
  26. python_delphi_lsp-1.1.1.dist-info/RECORD +0 -27
  27. python_delphi_lsp-1.1.1.dist-info/entry_points.txt +0 -3
  28. python_delphi_lsp-1.1.1.dist-info/top_level.txt +0 -1
  29. {delphiast → delphi_lsp}/binary.py +0 -0
  30. {delphiast → delphi_lsp}/comment_builder.py +0 -0
  31. {delphiast → delphi_lsp}/consts.py +0 -0
  32. {delphiast → delphi_lsp}/grammar.py +0 -0
  33. {delphiast → delphi_lsp}/lark_tokens.py +0 -0
  34. {delphiast → delphi_lsp}/nodes.py +0 -0
  35. {delphiast → delphi_lsp}/parser.py +0 -0
  36. {delphiast → delphi_lsp}/preprocessor.py +0 -0
  37. {delphiast → delphi_lsp}/semantic.py +0 -0
  38. {delphiast → delphi_lsp}/semantic_builder.py +0 -0
  39. {delphiast → delphi_lsp}/writer.py +0 -0
  40. {python_delphi_lsp-1.1.1.dist-info → python_delphi_lsp-2.0.1.dist-info}/WHEEL +0 -0
  41. {python_delphi_lsp-1.1.1.dist-info → python_delphi_lsp-2.0.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,3 +1,4 @@
1
+ from ._version import __version__
1
2
  from .binary import BinarySerializer, dumps, loads
2
3
  from .consts import AttributeName, SyntaxNodeType
3
4
  from .nodes import (
@@ -58,8 +59,20 @@ from .project_indexer import (
58
59
  UnitInfo,
59
60
  UnitParsedHook,
60
61
  )
62
+ from .metrics import (
63
+ CyclomaticMetrics,
64
+ HalsteadMetrics,
65
+ LineMetrics,
66
+ MetricProblem,
67
+ ProjectMetrics,
68
+ RoutineComplexity,
69
+ UnitMetrics,
70
+ analyze_project,
71
+ analyze_unit,
72
+ )
61
73
 
62
74
  __all__ = [
75
+ '__version__',
63
76
  'AttributeName',
64
77
  'BinarySerializer',
65
78
  'CommentNode',
@@ -132,6 +145,15 @@ __all__ = [
132
145
  'IncludeFileInfo',
133
146
  'GetUnitSyntaxHook',
134
147
  'UnitParsedHook',
148
+ 'MetricProblem',
149
+ 'LineMetrics',
150
+ 'HalsteadMetrics',
151
+ 'RoutineComplexity',
152
+ 'CyclomaticMetrics',
153
+ 'UnitMetrics',
154
+ 'ProjectMetrics',
155
+ 'analyze_unit',
156
+ 'analyze_project',
135
157
  'dumps',
136
158
  'loads',
137
159
  ]
delphi_lsp/_version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "2.0.1"
@@ -0,0 +1,243 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import BinaryIO, TextIO
9
+
10
+ from .agent_context import AgentContext
11
+ from .agent_layers import build_codebase_index, layer_payload, render_layer
12
+ from .agent_protocol import AgentProtocolError
13
+ from .agent_templates import install_opencode_support, install_skill
14
+
15
+
16
+ _MAX_WORKER_RECORD_BYTES = 1024 * 1024
17
+ _INVALID_JSON_MESSAGE = "Invalid JSON request."
18
+ _INVALID_ENCODING_MESSAGE = "Invalid UTF-8 request."
19
+ _REQUEST_TOO_LARGE_MESSAGE = "Request exceeds the 1 MiB limit."
20
+ _INTERNAL_ERROR_MESSAGE = "Internal request error."
21
+ _SOURCE_UNAVAILABLE_MESSAGE = "Selected source is unavailable."
22
+
23
+
24
+ def build_parser() -> argparse.ArgumentParser:
25
+ parser = argparse.ArgumentParser(
26
+ prog="delphi-lsp-agent",
27
+ description="Agent-facing Delphi/Object Pascal codebase navigation helpers.",
28
+ )
29
+ subcommands = parser.add_subparsers(dest="command", required=True)
30
+
31
+ view = subcommands.add_parser("view", help="Render a layered codebase view.")
32
+ view.add_argument("--root", type=Path, default=Path("."))
33
+ view.add_argument("--project-file", type=Path)
34
+ view.add_argument(
35
+ "--layer",
36
+ required=True,
37
+ choices=[
38
+ "overview",
39
+ "projects",
40
+ "units",
41
+ "unit",
42
+ "symbols",
43
+ "symbol",
44
+ "implementation",
45
+ "references",
46
+ "problems",
47
+ "metrics",
48
+ ],
49
+ )
50
+ view.add_argument("--query", default="")
51
+ view.add_argument("--format", default="markdown", choices=["markdown", "json"])
52
+ view.add_argument("--deep-projects", action="store_true", help="Deep-parse project dependencies for the projects layer.")
53
+ view.set_defaults(func=_view)
54
+
55
+ index = subcommands.add_parser("index", help="Materialize a JSON codebase index.")
56
+ index.add_argument("--root", type=Path, default=Path("."))
57
+ index.add_argument("--project-file", type=Path)
58
+ index.add_argument("--out", type=Path, default=Path(".delphi-lsp") / "agent-index" / "index.json")
59
+ index.set_defaults(func=_index)
60
+
61
+ skill = subcommands.add_parser("skill", help="Install agent skill templates.")
62
+ skill_commands = skill.add_subparsers(dest="skill_command", required=True)
63
+ skill_install = skill_commands.add_parser("install", help="Install .agents skill.")
64
+ skill_install.add_argument("--target", type=Path, default=Path("."))
65
+ skill_install.add_argument("--force", action="store_true")
66
+ skill_install.set_defaults(func=_skill_install)
67
+
68
+ opencode = subcommands.add_parser("opencode", help="Install opencode integration.")
69
+ opencode_commands = opencode.add_subparsers(dest="opencode_command", required=True)
70
+ opencode_install = opencode_commands.add_parser("install", help="Install .agents skill and opencode plugin.")
71
+ opencode_install.add_argument("--target", type=Path, default=Path("."))
72
+ opencode_install.add_argument("--python", default=sys.executable)
73
+ opencode_install.add_argument("--force", action="store_true")
74
+ opencode_install.add_argument("--write-config", action="store_true")
75
+ opencode_install.set_defaults(func=_opencode_install)
76
+
77
+ worker = subcommands.add_parser("worker", help="Serve Protocol v2 NDJSON requests.")
78
+ worker.add_argument("--root", type=Path, required=True)
79
+ worker.add_argument("--project-file", type=Path)
80
+ worker.set_defaults(func=_worker)
81
+
82
+ return parser
83
+
84
+
85
+ def main(argv: list[str] | None = None) -> int:
86
+ parser = build_parser()
87
+ args = parser.parse_args(argv)
88
+ try:
89
+ args.func(args)
90
+ if not getattr(sys.stdout, "closed", False):
91
+ sys.stdout.flush()
92
+ except BrokenPipeError:
93
+ _discard_broken_stdout()
94
+ os._exit(1)
95
+ return 0
96
+
97
+
98
+ def _discard_broken_stdout() -> None:
99
+ stdout = sys.stdout
100
+ try:
101
+ stdout_fd = stdout.fileno()
102
+ except (AttributeError, OSError, ValueError):
103
+ stdout_fd = None
104
+ if stdout_fd is not None:
105
+ devnull_fd = os.open(os.devnull, os.O_WRONLY)
106
+ try:
107
+ os.dup2(devnull_fd, stdout_fd)
108
+ finally:
109
+ os.close(devnull_fd)
110
+ replacement = open(os.devnull, "w", encoding=getattr(stdout, "encoding", None) or "utf-8")
111
+ sys.stdout = replacement
112
+ sys.__stdout__ = replacement
113
+ try:
114
+ stdout.close()
115
+ except (BrokenPipeError, OSError, ValueError):
116
+ pass
117
+
118
+
119
+ def _view(args: argparse.Namespace) -> None:
120
+ index = build_codebase_index(args.root, project_file=args.project_file, index_projects=args.deep_projects)
121
+ sys.stdout.write(render_layer(index, args.layer, query=args.query, output_format=args.format))
122
+
123
+
124
+ def _index(args: argparse.Namespace) -> None:
125
+ index = build_codebase_index(args.root, project_file=args.project_file, index_projects=True)
126
+ payload = {
127
+ "overview": layer_payload(index, "overview"),
128
+ "projects": layer_payload(index, "projects"),
129
+ "problems": layer_payload(index, "problems"),
130
+ }
131
+ args.out.parent.mkdir(parents=True, exist_ok=True)
132
+ args.out.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
133
+ print(args.out)
134
+
135
+
136
+ def _skill_install(args: argparse.Namespace) -> None:
137
+ skill_path = install_skill(args.target, force=args.force)
138
+ print(skill_path)
139
+
140
+
141
+ def _opencode_install(args: argparse.Namespace) -> None:
142
+ skill_path, plugin_path, config_path = install_opencode_support(
143
+ args.target,
144
+ python_executable=args.python,
145
+ force=args.force,
146
+ write_config=args.write_config,
147
+ )
148
+ print(skill_path)
149
+ print(plugin_path)
150
+ if config_path is not None:
151
+ print(config_path)
152
+
153
+
154
+ def _worker(args: argparse.Namespace) -> None:
155
+ context = AgentContext.open(args.root, args.project_file)
156
+ try:
157
+ _serve_worker(context, sys.stdin.buffer, sys.stdout.buffer, sys.stderr)
158
+ finally:
159
+ try:
160
+ sys.stdout.close()
161
+ except (BrokenPipeError, OSError) as error:
162
+ raise BrokenPipeError from error
163
+
164
+
165
+ def _serve_worker(context: AgentContext, input_stream: BinaryIO, output_stream: BinaryIO, error_stream: TextIO) -> None:
166
+ discarding_oversize_record = False
167
+ while True:
168
+ record = input_stream.readline(_MAX_WORKER_RECORD_BYTES + 1)
169
+ if not record:
170
+ return
171
+ if discarding_oversize_record:
172
+ if record.endswith(b"\n"):
173
+ discarding_oversize_record = False
174
+ continue
175
+ if not record.endswith(b"\n") and len(record) > _MAX_WORKER_RECORD_BYTES:
176
+ if record.endswith(b"\r") and input_stream.read(1) == b"\n":
177
+ record = record[:-1]
178
+ else:
179
+ discarding_oversize_record = True
180
+ _write_worker_message(
181
+ output_stream,
182
+ _worker_error("request_too_large", _REQUEST_TOO_LARGE_MESSAGE),
183
+ )
184
+ continue
185
+
186
+ record = record.rstrip(b"\r\n")
187
+ if len(record) > _MAX_WORKER_RECORD_BYTES:
188
+ _write_worker_message(
189
+ output_stream,
190
+ _worker_error("request_too_large", _REQUEST_TOO_LARGE_MESSAGE),
191
+ )
192
+ continue
193
+ try:
194
+ text = record.decode("utf-8")
195
+ except UnicodeDecodeError:
196
+ _write_worker_message(
197
+ output_stream,
198
+ _worker_error("invalid_encoding", _INVALID_ENCODING_MESSAGE),
199
+ )
200
+ continue
201
+ if not text.strip():
202
+ continue
203
+ try:
204
+ request = json.loads(text)
205
+ except json.JSONDecodeError:
206
+ _write_worker_message(output_stream, _worker_error("invalid_json", _INVALID_JSON_MESSAGE))
207
+ continue
208
+ try:
209
+ response = context.handle(request)
210
+ message = response.to_mapping()
211
+ except BrokenPipeError:
212
+ raise
213
+ except AgentProtocolError as error:
214
+ message = _SOURCE_UNAVAILABLE_MESSAGE if error.code == "source_unavailable" else error.message
215
+ message = _worker_error(error.code, message)
216
+ except Exception as error:
217
+ error_stream.write(f"{type(error).__name__}\n")
218
+ error_stream.flush()
219
+ message = _worker_error("internal_error", _INTERNAL_ERROR_MESSAGE)
220
+ _write_worker_message(output_stream, message)
221
+ def _worker_error(code: str, message: str) -> dict[str, object]:
222
+ return {"schema": 2, "error": {"code": code, "message": message}}
223
+
224
+
225
+ def _write_worker_message(output_stream: BinaryIO, message: object) -> None:
226
+ serialized = json.dumps(
227
+ message,
228
+ ensure_ascii=False,
229
+ sort_keys=True,
230
+ separators=(",", ":"),
231
+ allow_nan=False,
232
+ ).encode("utf-8")
233
+ try:
234
+ output_stream.write(serialized + b"\n")
235
+ output_stream.flush()
236
+ except BrokenPipeError:
237
+ raise
238
+ except OSError as error:
239
+ raise BrokenPipeError from error
240
+
241
+
242
+ if __name__ == "__main__":
243
+ raise SystemExit(main())