python-delphi-lsp 2.0.1__py3-none-any.whl → 2.0.2__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 CHANGED
@@ -1 +1 @@
1
- __version__ = "2.0.1"
1
+ __version__ = "2.0.2"
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("install", help="Install .agents skill and opencode plugin.")
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("--write-config", action="store_true")
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, config_path = install_opencode_support(
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
- if config_path is not None:
151
- print(config_path)
158
+ print(agent_path)
152
159
 
153
160
 
154
161
  def _worker(args: argparse.Namespace) -> None:
@@ -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-codebase-navigator"
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 | None]:
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
- config_path = target_path / "opencode.json" if write_config else None
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
- return skill_path, plugin_path, config_path
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
- if path.is_symlink():
161
- raise FileExistsError(f"Generated destination must not be a symbolic link: {path}")
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-codebase-navigator
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,39 @@ 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: subagent
243
+ temperature: 0
244
+ tools:
245
+ delphi_codebase: true
246
+ skill: true
247
+ lsp: false
248
+ bash: false
249
+ read: false
250
+ glob: false
251
+ grep: false
252
+ edit: false
253
+ write: false
254
+ task: false
255
+ webfetch: false
256
+ todowrite: false
257
+ permission:
258
+ "*": deny
259
+ delphi_codebase: allow
260
+ skill:
261
+ "*": deny
262
+ python-delphi-lsp: allow
263
+ ---
264
+
265
+ Load `python-delphi-lsp` first, then use only `delphi_codebase` for Delphi and
266
+ Object Pascal codebase inspection. Do not use `lsp`, `bash`, `read`, `glob`,
267
+ `grep`, `edit`, `write`, `task`, `webfetch`, or `todowrite`. Preserve returned
268
+ citations exactly and report partial or ambiguous semantic evidence explicitly.
269
+ """
270
+
271
+
195
272
  def _opencode_plugin(python_executable: str) -> str:
196
273
  python_json = json.dumps(python_executable)
197
274
  template = '''import { tool, type Plugin } from "@opencode-ai/plugin"
@@ -525,56 +602,4 @@ export const DelphiCodebasePlugin: Plugin = async (_input) => {
525
602
  return template.replace("__PYTHON_EXECUTABLE__", python_json)
526
603
 
527
604
 
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
605
  __all__ = ["SKILL_NAME", "install_opencode_support", "install_skill"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-delphi-lsp
3
- Version: 2.0.1
3
+ Version: 2.0.2
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.1 is authored by Dark Light and supports Windows, macOS, and Linux.
44
+ Version 2.0.2 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 both integration files, while
191
- `--write-config` additionally writes the restricted agent configuration.
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,47 @@ 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 . --write-config
229
+ delphi-lsp-agent opencode install --target .
229
230
  ```
230
231
 
231
232
  It writes:
232
233
 
233
234
  ```text
234
- .agents/skills/delphi-codebase-navigator/SKILL.md
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 generated configuration enables only the named
239
- `delphi-codebase-navigator` skill and `delphi_codebase`. It denies
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 looks like this; providers and unrelated agents stay
249
- unchanged:
250
-
251
- ```json
252
- {
253
- "agent": {
254
- "vllm-delphi-codebase": {
255
- "tools": {
256
- "delphi_codebase": true, "skill": true, "lsp": false,
257
- "bash": false, "read": false, "glob": false, "grep": false
258
- },
259
- "permission": {
260
- "delphi_codebase": "allow",
261
- "skill": {"*": "deny", "delphi-codebase-navigator": "allow"},
262
- "lsp": "deny"
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: subagent
258
+ temperature: 0
259
+ permission:
260
+ "*": deny
261
+ delphi_codebase: allow
262
+ skill:
263
+ "*": deny
264
+ python-delphi-lsp: allow
265
+ ---
267
266
  ```
268
267
 
269
- Select `vllm-delphi-codebase`, ask it to load
270
- `delphi-codebase-navigator`, then use `delphi_codebase` actions such as
268
+ Select `python-delphi-lsp`, ask it to load the `python-delphi-lsp` skill, then
269
+ use `delphi_codebase` actions such as
271
270
  `open`, `find`, `focus`, and `inspect`. Use semantic tool calls, not raw
272
271
  source tools.
273
272
 
@@ -1,12 +1,12 @@
1
1
  delphi_lsp/__init__.py,sha256=Gtoeg_D3hLGCFi34wdYiKrIByAbkWe1zS84v6_KJ6_4,3768
2
- delphi_lsp/_version.py,sha256=wAxkK8w13vqoF47A8iqWdSlIgRRXmZiQ0R4wePZfzhs,22
3
- delphi_lsp/agent_cli.py,sha256=FY5h_rmbPLBAtr8NWL5IyzqEaJBJm7Y5IQptvlt8l4Y,9022
2
+ delphi_lsp/_version.py,sha256=tATvJM5shAzfspHYjdVwpV2w3-gDA119NlEYi5X2lFY,22
3
+ delphi_lsp/agent_cli.py,sha256=Mma4C1Ca_Hd_9dl0mePT7uIypw6j6gDWdKu9XxIMXQQ,9226
4
4
  delphi_lsp/agent_context.py,sha256=VCWiqIhLPQX6-_4P_kYHLKMZK7ia0c1mbFC7RcbQsOY,73316
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=U6ldX6RpdpttF5D0QSAi_OsqkcCe30Jc75hyhV3geHQ,21305
9
+ delphi_lsp/agent_templates.py,sha256=vPVJPlDfTU1P95-fPqHwyrDBw6zKtNMidjCdY16QHGk,21782
10
10
  delphi_lsp/agent_workspace.py,sha256=nnLwjLzkh_gsBxz8Czl_Q1OHRya7mHENGhmBi8br6AE,23527
11
11
  delphi_lsp/binary.py,sha256=40mEpE0SBYmifwEoNHwSU7IU_D2usVyWYWHJ73aDfoo,8529
12
12
  delphi_lsp/comment_builder.py,sha256=XvFW7WTbYy7yGN3MG40gtabTvhGj1ea1MYkySDbVMDA,832
@@ -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.1.dist-info/licenses/LICENSE,sha256=-rPda9qyJvHAhjCx3ZF-Efy07F4eAg4sFvg6ChOGPoU,16726
30
- python_delphi_lsp-2.0.1.dist-info/METADATA,sha256=4CthXJEauAQJm7-BDm1iDnMdj9-Eovu-py69_ALNiPY,14621
31
- python_delphi_lsp-2.0.1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
32
- python_delphi_lsp-2.0.1.dist-info/entry_points.txt,sha256=rh_yGH7diowge_acQcST87A-ewH1QezEsAYC8T5ineY,103
33
- python_delphi_lsp-2.0.1.dist-info/top_level.txt,sha256=0Zp0sjw5Qtic3EQX9zNeegURga8NuXk6c2e9PN-3OTs,11
34
- python_delphi_lsp-2.0.1.dist-info/RECORD,,
29
+ python_delphi_lsp-2.0.2.dist-info/licenses/LICENSE,sha256=-rPda9qyJvHAhjCx3ZF-Efy07F4eAg4sFvg6ChOGPoU,16726
30
+ python_delphi_lsp-2.0.2.dist-info/METADATA,sha256=rMiG6A1pIFWKPfEFEqCX3ivspfyHx-KhUJyYk2R7NDA,14696
31
+ python_delphi_lsp-2.0.2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
32
+ python_delphi_lsp-2.0.2.dist-info/entry_points.txt,sha256=rh_yGH7diowge_acQcST87A-ewH1QezEsAYC8T5ineY,103
33
+ python_delphi_lsp-2.0.2.dist-info/top_level.txt,sha256=0Zp0sjw5Qtic3EQX9zNeegURga8NuXk6c2e9PN-3OTs,11
34
+ python_delphi_lsp-2.0.2.dist-info/RECORD,,