python-delphi-lsp 1.1.0__tar.gz → 1.1.1__tar.gz

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 (112) hide show
  1. {python_delphi_lsp-1.1.0/python_delphi_lsp.egg-info → python_delphi_lsp-1.1.1}/PKG-INFO +21 -11
  2. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/README.md +20 -10
  3. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/agent_cli.py +15 -1
  4. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/agent_layers.py +167 -1
  5. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/agent_templates.py +28 -29
  6. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/pyproject.toml +1 -1
  7. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1/python_delphi_lsp.egg-info}/PKG-INFO +21 -11
  8. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/scripts/bootstrap_vllm_codebase_skill_test.py +13 -5
  9. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_agent_codebase.py +84 -5
  10. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_bootstrap_vllm_codebase_skill.py +12 -0
  11. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_package_metadata.py +2 -2
  12. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/LICENSE +0 -0
  13. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/MANIFEST.in +0 -0
  14. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/__init__.py +0 -0
  15. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/binary.py +0 -0
  16. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/comment_builder.py +0 -0
  17. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/consts.py +0 -0
  18. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/grammar.py +0 -0
  19. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/lark_builder.py +0 -0
  20. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/lark_tokens.py +0 -0
  21. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/lsp_server.py +0 -0
  22. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/nodes.py +0 -0
  23. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/parser.py +0 -0
  24. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/preprocessor.py +0 -0
  25. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/project_discovery.py +0 -0
  26. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/project_indexer.py +0 -0
  27. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/semantic.py +0 -0
  28. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/semantic_builder.py +0 -0
  29. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/source_reader.py +0 -0
  30. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/workspace.py +0 -0
  31. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/delphiast/writer.py +0 -0
  32. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/opencode.json +0 -0
  33. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/python_delphi_lsp.egg-info/SOURCES.txt +0 -0
  34. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/python_delphi_lsp.egg-info/dependency_links.txt +0 -0
  35. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/python_delphi_lsp.egg-info/entry_points.txt +0 -0
  36. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/python_delphi_lsp.egg-info/requires.txt +0 -0
  37. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/python_delphi_lsp.egg-info/top_level.txt +0 -0
  38. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/scripts/audit_delphi_language_features.py +0 -0
  39. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/scripts/bootstrap_vllm_opencode_test.ps1 +0 -0
  40. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/scripts/bootstrap_vllm_opencode_test.py +0 -0
  41. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/scripts/bootstrap_vllm_opencode_test.sh +0 -0
  42. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/scripts/check_ornith_cache.py +0 -0
  43. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/scripts/generate_release_evidence.py +0 -0
  44. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/scripts/ollama/ornith-lspctx.Modelfile +0 -0
  45. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/scripts/prepare_ornith_cache.py +0 -0
  46. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/scripts/run_opencode_lsp_probe.py +0 -0
  47. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/scripts/start_ornith_vllm.sh +0 -0
  48. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/scripts/summarize_opencode_request_payloads.py +0 -0
  49. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/setup.cfg +0 -0
  50. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/DeprecatedOnConst.pas +0 -0
  51. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/VariantRecordFieldAttributes.pas +0 -0
  52. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/alignedrecords.pas +0 -0
  53. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/constset.pas +0 -0
  54. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/deprecatedtype.pas +0 -0
  55. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/dottedtypes.pas +0 -0
  56. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/endtoken.pas +0 -0
  57. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/experimentals.pas +0 -0
  58. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/externalfunction.pas +0 -0
  59. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/finalizationinitializationexports.pas +0 -0
  60. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/forwardoverloaded.pas +0 -0
  61. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/forwardwithoutsemicolon.pas +0 -0
  62. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/genericconstraints.pas +0 -0
  63. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/genericinterfacemethoddelegation.pas +0 -0
  64. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/implementsgenerictype.pas +0 -0
  65. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/include file2.inc +0 -0
  66. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/includefile.inc +0 -0
  67. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/includefile.pas +0 -0
  68. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/managedrecords.pas +0 -0
  69. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/messagemethod.pas +0 -0
  70. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/multiline.pas +0 -0
  71. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/nonalignedrecords.pas +0 -0
  72. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/numbers.pas +0 -0
  73. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/pointerchars.pas +0 -0
  74. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/properties.pas +0 -0
  75. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/strictvisibility.pas +0 -0
  76. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/tryexcept.pas +0 -0
  77. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/umlauts.pas +0 -0
  78. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/legacy_snippets/whitespacearoundifdefcondition.pas +0 -0
  79. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/library_demo.dpr +0 -0
  80. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/package_demo.dpk +0 -0
  81. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/program_demo.dpr +0 -0
  82. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/unit_advanced.pas +0 -0
  83. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/unit_attributes.pas +0 -0
  84. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/unit_basic.pas +0 -0
  85. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/unit_consumer.pas +0 -0
  86. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/unit_generics.pas +0 -0
  87. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/unit_inheritance.pas +0 -0
  88. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/unit_math.pas +0 -0
  89. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/unit_properties.pas +0 -0
  90. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/unit_sections.pas +0 -0
  91. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/unit_statements.pas +0 -0
  92. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/unit_types.pas +0 -0
  93. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/unit_unresolved.pas +0 -0
  94. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/fixtures/unit_with.pas +0 -0
  95. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_bootstrap_vllm_opencode.py +0 -0
  96. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_diagnostics.py +0 -0
  97. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_language_feature_matrix.py +0 -0
  98. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_legacy_snippets.py +0 -0
  99. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_lsp_features.py +0 -0
  100. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_lsp_support.py +0 -0
  101. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_opencode_config.py +0 -0
  102. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_opencode_probe.py +0 -0
  103. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_opencode_request_payloads.py +0 -0
  104. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_ornith_cache_prepare.py +0 -0
  105. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_parser.py +0 -0
  106. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_preprocessor.py +0 -0
  107. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_project_discovery.py +0 -0
  108. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_project_indexer.py +0 -0
  109. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_release_evidence.py +0 -0
  110. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_semantic.py +0 -0
  111. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_vllm_start_script.py +0 -0
  112. {python_delphi_lsp-1.1.0 → python_delphi_lsp-1.1.1}/tests/test_workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-delphi-lsp
3
- Version: 1.1.0
3
+ Version: 1.1.1
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
@@ -191,13 +191,19 @@ delphi-lsp-agent view --root . --layer overview
191
191
  delphi-lsp-agent view --root . --layer projects
192
192
  delphi-lsp-agent view --root . --layer unit --query Mega100kUnit
193
193
  delphi-lsp-agent view --root . --layer symbols --query TWorker --format json
194
+ delphi-lsp-agent view --root . --layer implementation --query TWorker.Run
195
+ delphi-lsp-agent view --root . --layer implementation --query TWorker --format json
194
196
  delphi-lsp-agent view --root . --layer problems
195
197
  ```
196
198
 
197
199
  Available layers are `overview`, `projects`, `units`, `unit`, `symbols`,
198
- `symbol`, `references`, and `problems`. The output includes file and line
199
- citations, declarations, ownership, visibility, type information, and
200
- dependency/problem summaries. Routine bodies are not emitted.
200
+ `symbol`, `implementation`, `references`, and `problems`. The output includes
201
+ file and line citations, declarations, ownership, visibility, type information,
202
+ and dependency/problem summaries. Outline layers keep routine bodies out of the
203
+ agent context. Use `implementation` with a concrete class, routine, or member
204
+ query to read only that complete source fragment from the original file. For a
205
+ class query, the layer returns the class declaration plus matching method
206
+ implementations; for a routine query, it returns the full routine body.
201
207
 
202
208
  ## opencode Usage
203
209
 
@@ -380,13 +386,17 @@ python scripts/bootstrap_vllm_codebase_skill_test.py --start-vllm
380
386
  ```
381
387
 
382
388
  This skill probe defaults the vLLM helper to `MAX_MODEL_LEN=32768`, which was
383
- the stable local Metal setting for the complete skill + `delphi_codebase` tool
384
- call. Override with `--max-model-len` when your machine has enough headroom.
385
-
386
- The probe requires opencode to load `delphi-codebase-navigator`, call
387
- `delphi_codebase`, and find `MegaProc02500`. It fails if opencode uses `bash`,
388
- `read`, `glob`, `grep`, `edit`, `write`, `task`, `webfetch`, or `todowrite`
389
- before the required evidence is complete.
389
+ the stable local Metal setting for the reduced codebase-agent prompt plus the
390
+ `delphi_codebase` tool call. Override with `--max-model-len` when your machine
391
+ has enough headroom.
392
+
393
+ The probe verifies that the `delphi-codebase-navigator` skill is installed, then
394
+ requires opencode to call `delphi_codebase`, find `MegaProc02500`, and read the
395
+ method body through the `implementation` layer. The vLLM test agent keeps
396
+ opencode's generic `skill` tool disabled so the model context stays focused on
397
+ the Delphi navigation tool. The probe fails if opencode uses `bash`, `read`,
398
+ `glob`, `grep`, `edit`, `write`, `task`, `webfetch`, or `todowrite` before the
399
+ required evidence is complete.
390
400
 
391
401
  ## Verification
392
402
 
@@ -152,13 +152,19 @@ delphi-lsp-agent view --root . --layer overview
152
152
  delphi-lsp-agent view --root . --layer projects
153
153
  delphi-lsp-agent view --root . --layer unit --query Mega100kUnit
154
154
  delphi-lsp-agent view --root . --layer symbols --query TWorker --format json
155
+ delphi-lsp-agent view --root . --layer implementation --query TWorker.Run
156
+ delphi-lsp-agent view --root . --layer implementation --query TWorker --format json
155
157
  delphi-lsp-agent view --root . --layer problems
156
158
  ```
157
159
 
158
160
  Available layers are `overview`, `projects`, `units`, `unit`, `symbols`,
159
- `symbol`, `references`, and `problems`. The output includes file and line
160
- citations, declarations, ownership, visibility, type information, and
161
- dependency/problem summaries. Routine bodies are not emitted.
161
+ `symbol`, `implementation`, `references`, and `problems`. The output includes
162
+ file and line citations, declarations, ownership, visibility, type information,
163
+ and dependency/problem summaries. Outline layers keep routine bodies out of the
164
+ agent context. Use `implementation` with a concrete class, routine, or member
165
+ query to read only that complete source fragment from the original file. For a
166
+ class query, the layer returns the class declaration plus matching method
167
+ implementations; for a routine query, it returns the full routine body.
162
168
 
163
169
  ## opencode Usage
164
170
 
@@ -341,13 +347,17 @@ python scripts/bootstrap_vllm_codebase_skill_test.py --start-vllm
341
347
  ```
342
348
 
343
349
  This skill probe defaults the vLLM helper to `MAX_MODEL_LEN=32768`, which was
344
- the stable local Metal setting for the complete skill + `delphi_codebase` tool
345
- call. Override with `--max-model-len` when your machine has enough headroom.
346
-
347
- The probe requires opencode to load `delphi-codebase-navigator`, call
348
- `delphi_codebase`, and find `MegaProc02500`. It fails if opencode uses `bash`,
349
- `read`, `glob`, `grep`, `edit`, `write`, `task`, `webfetch`, or `todowrite`
350
- before the required evidence is complete.
350
+ the stable local Metal setting for the reduced codebase-agent prompt plus the
351
+ `delphi_codebase` tool call. Override with `--max-model-len` when your machine
352
+ has enough headroom.
353
+
354
+ The probe verifies that the `delphi-codebase-navigator` skill is installed, then
355
+ requires opencode to call `delphi_codebase`, find `MegaProc02500`, and read the
356
+ method body through the `implementation` layer. The vLLM test agent keeps
357
+ opencode's generic `skill` tool disabled so the model context stays focused on
358
+ the Delphi navigation tool. The probe fails if opencode uses `bash`, `read`,
359
+ `glob`, `grep`, `edit`, `write`, `task`, `webfetch`, or `todowrite` before the
360
+ required evidence is complete.
351
361
 
352
362
  ## Verification
353
363
 
@@ -19,7 +19,21 @@ def build_parser() -> argparse.ArgumentParser:
19
19
  view = subcommands.add_parser("view", help="Render a layered codebase view.")
20
20
  view.add_argument("--root", type=Path, default=Path("."))
21
21
  view.add_argument("--project-file", type=Path)
22
- view.add_argument("--layer", required=True, choices=["overview", "projects", "units", "unit", "symbols", "symbol", "references", "problems"])
22
+ view.add_argument(
23
+ "--layer",
24
+ required=True,
25
+ choices=[
26
+ "overview",
27
+ "projects",
28
+ "units",
29
+ "unit",
30
+ "symbols",
31
+ "symbol",
32
+ "implementation",
33
+ "references",
34
+ "problems",
35
+ ],
36
+ )
23
37
  view.add_argument("--query", default="")
24
38
  view.add_argument("--format", default="markdown", choices=["markdown", "json"])
25
39
  view.add_argument("--deep-projects", action="store_true", help="Deep-parse project dependencies for the projects layer.")
@@ -8,7 +8,7 @@ import json
8
8
  from .lsp_server import build_outline_semantic_model, outline_large_source
9
9
  from .project_discovery import DelphiProjectDiscovery, discover_delphi_project
10
10
  from .project_indexer import ProjectIndexResult, ProjectIndexer
11
- from .semantic import Scope, SourceRange, Symbol, SymbolIndex
11
+ from .semantic import Scope, SourceRange, Symbol, SymbolIndex, SymbolKind
12
12
  from .semantic_builder import SemanticModel
13
13
  from .source_reader import read_source_text
14
14
 
@@ -92,6 +92,8 @@ def layer_payload(index: CodebaseIndex, layer: str, *, query: str = "") -> dict[
92
92
  return _symbols_payload(index, query=query)
93
93
  if normalized_layer == "symbol":
94
94
  return _symbol_payload(index, query=query)
95
+ if normalized_layer == "implementation":
96
+ return _implementation_payload(index, query=query)
95
97
  if normalized_layer == "references":
96
98
  return _references_payload(index, query=query)
97
99
  if normalized_layer == "problems":
@@ -206,6 +208,142 @@ def _symbol_payload(index: CodebaseIndex, *, query: str) -> dict[str, Any]:
206
208
  return {"layer": "symbol", "root": index.root, "query": query, "items": matches[:50]}
207
209
 
208
210
 
211
+ def _implementation_payload(index: CodebaseIndex, *, query: str) -> dict[str, Any]:
212
+ needle = query.casefold().strip()
213
+ if not needle:
214
+ return {
215
+ "layer": "implementation",
216
+ "root": index.root,
217
+ "query": query,
218
+ "items": [],
219
+ "message": "Pass a class, routine, or member name in query to read focused source.",
220
+ }
221
+
222
+ matches = [symbol for symbol in _all_symbols(index) if symbol.name.casefold() == needle]
223
+ if not matches:
224
+ matches = [
225
+ symbol
226
+ for symbol in _all_symbols(index)
227
+ if symbol.kind.value != "unit" and needle in symbol.name.casefold()
228
+ ]
229
+
230
+ items: list[dict[str, Any]] = []
231
+ source_cache: dict[str, list[str]] = {}
232
+ seen_keys: set[tuple[str, str, int, str]] = set()
233
+ for symbol in matches:
234
+ item = _implementation_item(index, symbol, source_cache)
235
+ if item is None:
236
+ continue
237
+ key = (item["name"].casefold(), item["path"], item["line"], item["kind"])
238
+ if key in seen_keys:
239
+ continue
240
+ seen_keys.add(key)
241
+ items.append(item)
242
+ items.sort(key=lambda item: (item["path"].casefold(), item["line"], item["name"].casefold()))
243
+ return {"layer": "implementation", "root": index.root, "query": query, "items": items[:50]}
244
+
245
+
246
+ def _implementation_item(
247
+ index: CodebaseIndex,
248
+ symbol: Symbol,
249
+ source_cache: dict[str, list[str]],
250
+ ) -> dict[str, Any] | None:
251
+ fragments: list[dict[str, Any]] = []
252
+ if symbol.kind in {SymbolKind.CLASS, SymbolKind.RECORD, SymbolKind.INTERFACE}:
253
+ declaration = _source_fragment(symbol.decl_range, "declaration", source_cache)
254
+ if declaration is not None:
255
+ fragments.append(declaration)
256
+ fragments.extend(_implementation_fragments_for_type(index, symbol, source_cache))
257
+ elif symbol.kind in _ROUTINE_KINDS:
258
+ fragment = _source_fragment(symbol.decl_range, "implementation", source_cache)
259
+ if fragment is not None:
260
+ fragments.append(fragment)
261
+ else:
262
+ fragment = _source_fragment(symbol.decl_range, "declaration", source_cache)
263
+ if fragment is not None:
264
+ fragments.append(fragment)
265
+ fragments.extend(_implementation_fragments_for_member(index, symbol, source_cache))
266
+ if not fragments:
267
+ return None
268
+ item = _symbol_item(symbol)
269
+ item["fragments"] = fragments
270
+ return item
271
+
272
+
273
+ def _implementation_fragments_for_type(
274
+ index: CodebaseIndex,
275
+ symbol: Symbol,
276
+ source_cache: dict[str, list[str]],
277
+ ) -> list[dict[str, Any]]:
278
+ prefix = f"{symbol.name.casefold()}."
279
+ fragments: list[dict[str, Any]] = []
280
+ seen_ranges: set[SourceRange] = set()
281
+ for candidate in _all_symbols(index):
282
+ if candidate.kind not in _ROUTINE_KINDS:
283
+ continue
284
+ if candidate.decl_range.file_name != symbol.decl_range.file_name:
285
+ continue
286
+ if not candidate.name.casefold().startswith(prefix):
287
+ continue
288
+ fragment = _source_fragment(candidate.decl_range, "implementation", source_cache)
289
+ if fragment is None or candidate.decl_range in seen_ranges:
290
+ continue
291
+ seen_ranges.add(candidate.decl_range)
292
+ fragment["symbol"] = candidate.name
293
+ fragments.append(fragment)
294
+ fragments.sort(key=lambda item: (item["range"]["start_line"], item["range"]["start_col"]))
295
+ return fragments
296
+
297
+
298
+ def _implementation_fragments_for_member(
299
+ index: CodebaseIndex,
300
+ symbol: Symbol,
301
+ source_cache: dict[str, list[str]],
302
+ ) -> list[dict[str, Any]]:
303
+ owner = symbol.scope.owner
304
+ if owner is None or owner.kind not in {SymbolKind.CLASS, SymbolKind.RECORD, SymbolKind.INTERFACE}:
305
+ return []
306
+ qualified_name = f"{owner.name.casefold()}.{symbol.name.casefold()}"
307
+ fragments: list[dict[str, Any]] = []
308
+ for candidate in _all_symbols(index):
309
+ if candidate.kind not in _ROUTINE_KINDS:
310
+ continue
311
+ if candidate.decl_range.file_name != symbol.decl_range.file_name:
312
+ continue
313
+ if candidate.name.casefold() != qualified_name:
314
+ continue
315
+ fragment = _source_fragment(candidate.decl_range, "implementation", source_cache)
316
+ if fragment is not None:
317
+ fragment["symbol"] = candidate.name
318
+ fragments.append(fragment)
319
+ return fragments
320
+
321
+
322
+ def _source_fragment(
323
+ source_range: SourceRange,
324
+ fragment_kind: str,
325
+ source_cache: dict[str, list[str]],
326
+ ) -> dict[str, Any] | None:
327
+ lines = source_cache.get(source_range.file_name)
328
+ if lines is None:
329
+ try:
330
+ text = read_source_text(Path(source_range.file_name))
331
+ except (OSError, UnicodeError):
332
+ return None
333
+ lines = text.splitlines(keepends=True)
334
+ source_cache[source_range.file_name] = lines
335
+ if source_range.start_line < 1 or source_range.start_line > len(lines):
336
+ return None
337
+ end_line = min(max(source_range.end_line, source_range.start_line), len(lines))
338
+ snippet = "".join(lines[source_range.start_line - 1 : end_line]).rstrip("\r\n")
339
+ return {
340
+ "fragment_kind": fragment_kind,
341
+ "range": _range_item(source_range),
342
+ "line_count": end_line - source_range.start_line + 1,
343
+ "text": snippet,
344
+ }
345
+
346
+
209
347
  def _references_payload(index: CodebaseIndex, *, query: str) -> dict[str, Any]:
210
348
  needle = query.casefold().strip()
211
349
  items = []
@@ -265,6 +403,14 @@ def _symbol_item(symbol: Symbol) -> dict[str, Any]:
265
403
  }
266
404
 
267
405
 
406
+ _ROUTINE_KINDS = {
407
+ SymbolKind.PROCEDURE,
408
+ SymbolKind.FUNCTION,
409
+ SymbolKind.CONSTRUCTOR,
410
+ SymbolKind.DESTRUCTOR,
411
+ }
412
+
413
+
268
414
  def _range_item(source_range: SourceRange) -> dict[str, int | str]:
269
415
  return {
270
416
  "path": source_range.file_name,
@@ -312,6 +458,26 @@ def _render_markdown(payload: dict[str, Any]) -> str:
312
458
  lines.append(
313
459
  f"- `{symbol['name']}` {symbol['kind']} at `{symbol['path']}:{symbol['line']}`"
314
460
  )
461
+ elif payload["layer"] == "implementation":
462
+ if payload.get("message"):
463
+ lines.append(payload["message"])
464
+ for item in payload["items"]:
465
+ lines.append(f"## {item['name']}")
466
+ lines.append(f"- Kind: `{item['kind']}`")
467
+ lines.append(f"- Path: `{item['path']}`")
468
+ for fragment in item["fragments"]:
469
+ source_range = fragment["range"]
470
+ lines.append("")
471
+ lines.append(
472
+ f"### {fragment['fragment_kind']} `{source_range['path']}:{source_range['start_line']}`"
473
+ )
474
+ if fragment.get("symbol"):
475
+ lines.append(f"- Symbol: `{fragment['symbol']}`")
476
+ lines.append(f"- Lines: {fragment['line_count']}")
477
+ lines.append("")
478
+ lines.append("```pascal")
479
+ lines.append(fragment["text"])
480
+ lines.append("```")
315
481
  elif payload["layer"] == "projects":
316
482
  for item in payload["items"]:
317
483
  lines.append(f"## `{item['path']}`")
@@ -58,6 +58,7 @@ Use this skill when a task asks you to understand a Delphi/Object Pascal codebas
58
58
  - Do not use shell text-search commands for Delphi navigation.
59
59
  - Use the `delphi_codebase` opencode tool when it is available.
60
60
  - If the tool is not available, run `delphi-lsp-agent view` commands only after the user permits command execution.
61
+ - When a task asks for codebase inspection, call `delphi_codebase` first; do not write "let me inspect" text before the tool call.
61
62
  - Cite files and line numbers from the layer output.
62
63
 
63
64
  ## Workflow
@@ -67,14 +68,16 @@ Use this skill when a task asks you to understand a Delphi/Object Pascal codebas
67
68
  3. Use `units` and `unit` to inspect outlines without routine bodies.
68
69
  4. Use `symbols` to find a type, routine, property, field, constant, or variable.
69
70
  5. Use `symbol` for a focused symbol card and children.
70
- 6. Use `references` for definition/reference-oriented evidence.
71
- 7. Use `problems` before concluding that code is missing.
71
+ 6. Use `implementation` with a concrete class, routine, or member query when you need the complete source for that one symbol.
72
+ 7. Use `references` for definition/reference-oriented evidence.
73
+ 8. Use `problems` before concluding that code is missing.
72
74
 
73
75
  ## Tool examples
74
76
 
75
77
  - `delphi_codebase({ "layer": "overview" })`
76
78
  - `delphi_codebase({ "layer": "unit", "query": "Worker" })`
77
79
  - `delphi_codebase({ "layer": "symbols", "query": "TWorker", "format": "json" })`
80
+ - `delphi_codebase({ "layer": "implementation", "query": "TWorker.Run" })`
78
81
 
79
82
  Prefer narrow follow-up queries over broad output.
80
83
  """
@@ -82,37 +85,26 @@ Prefer narrow follow-up queries over broad output.
82
85
 
83
86
  def _opencode_tool(python_executable: str) -> str:
84
87
  python_json = json.dumps(python_executable)
85
- return f"""const PYTHON = {python_json}
88
+ return f"""import {{ tool }} from "@opencode-ai/plugin"
89
+
90
+ const PYTHON = {python_json}
86
91
 
87
92
  async function streamToText(stream: ReadableStream | null): Promise<string> {{
88
93
  if (!stream) return ""
89
94
  return await new Response(stream).text()
90
95
  }}
91
96
 
92
- export default {{
97
+ export default tool({{
93
98
  description: "Inspect a Delphi/Object Pascal codebase through python-delphi-lsp layered semantic views.",
94
99
  args: {{
95
- layer: {{
96
- type: "string",
97
- enum: ["overview", "projects", "units", "unit", "symbols", "symbol", "references", "problems"],
98
- description: "Layer to inspect",
99
- }},
100
- query: {{
101
- type: "string",
102
- description: "Optional unit, file, or symbol filter",
103
- }},
104
- root: {{
105
- type: "string",
106
- description: "Workspace root; defaults to current worktree or directory",
107
- }},
108
- format: {{
109
- type: "string",
110
- enum: ["markdown", "json"],
111
- description: "Output format",
112
- }},
100
+ layer: tool.schema.enum(["overview", "projects", "units", "unit", "symbols", "symbol", "implementation", "references", "problems"]).describe("Layer to inspect"),
101
+ query: tool.schema.string().optional().describe("Unit, file, or symbol filter"),
102
+ root: tool.schema.string().optional().describe("Workspace root; defaults to current worktree or directory"),
103
+ format: tool.schema.enum(["markdown", "json"]).optional().describe("Output format; defaults to markdown"),
113
104
  }},
114
105
  async execute(args, context) {{
115
- const root = args.root ?? context.worktree ?? context.directory
106
+ const contextDirectory = context.directory && context.directory !== "/" ? context.directory : undefined
107
+ const root = args.root ?? context.worktree ?? contextDirectory ?? process.cwd()
116
108
  const command = [
117
109
  PYTHON,
118
110
  "-m",
@@ -143,7 +135,7 @@ export default {{
143
135
  }}
144
136
  return stdout.trim()
145
137
  }},
146
- }}
138
+ }})
147
139
  """
148
140
 
149
141
 
@@ -155,10 +147,17 @@ def _merge_opencode_config(config_path: Path) -> None:
155
147
  agents = config.setdefault("agent", {})
156
148
  agents["vllm-delphi-codebase"] = {
157
149
  "description": "Use the Delphi codebase navigator skill and tool without direct filesystem source inspection.",
150
+ "temperature": 0,
151
+ "prompt": (
152
+ "You are a tool-calling assistant. When the user asks for a tool call, call the matching tool. "
153
+ "The matching tool for Delphi/Object Pascal codebase inspection is delphi_codebase. "
154
+ "Use delphi_codebase before answering Delphi codebase questions. "
155
+ "Use implementation for complete source of one concrete class, routine, or member."
156
+ ),
158
157
  "tools": {
159
158
  "delphi_codebase": True,
160
- "skill": True,
161
- "lsp": True,
159
+ "skill": False,
160
+ "lsp": False,
162
161
  "bash": False,
163
162
  "read": False,
164
163
  "glob": False,
@@ -170,9 +169,9 @@ def _merge_opencode_config(config_path: Path) -> None:
170
169
  "todowrite": False,
171
170
  },
172
171
  "permission": {
173
- "skill": {
174
- SKILL_NAME: "allow",
175
- }
172
+ "delphi_codebase": "allow",
173
+ "lsp": "deny",
174
+ "skill": "deny",
176
175
  },
177
176
  }
178
177
  config_path.write_text(json.dumps(config, indent=2, sort_keys=True) + "\n", encoding="utf-8")
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-delphi-lsp"
7
- version = "1.1.0"
7
+ version = "1.1.1"
8
8
  description = "Python Delphi/Object Pascal parser, semantic indexer, and language server."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-delphi-lsp
3
- Version: 1.1.0
3
+ Version: 1.1.1
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
@@ -191,13 +191,19 @@ delphi-lsp-agent view --root . --layer overview
191
191
  delphi-lsp-agent view --root . --layer projects
192
192
  delphi-lsp-agent view --root . --layer unit --query Mega100kUnit
193
193
  delphi-lsp-agent view --root . --layer symbols --query TWorker --format json
194
+ delphi-lsp-agent view --root . --layer implementation --query TWorker.Run
195
+ delphi-lsp-agent view --root . --layer implementation --query TWorker --format json
194
196
  delphi-lsp-agent view --root . --layer problems
195
197
  ```
196
198
 
197
199
  Available layers are `overview`, `projects`, `units`, `unit`, `symbols`,
198
- `symbol`, `references`, and `problems`. The output includes file and line
199
- citations, declarations, ownership, visibility, type information, and
200
- dependency/problem summaries. Routine bodies are not emitted.
200
+ `symbol`, `implementation`, `references`, and `problems`. The output includes
201
+ file and line citations, declarations, ownership, visibility, type information,
202
+ and dependency/problem summaries. Outline layers keep routine bodies out of the
203
+ agent context. Use `implementation` with a concrete class, routine, or member
204
+ query to read only that complete source fragment from the original file. For a
205
+ class query, the layer returns the class declaration plus matching method
206
+ implementations; for a routine query, it returns the full routine body.
201
207
 
202
208
  ## opencode Usage
203
209
 
@@ -380,13 +386,17 @@ python scripts/bootstrap_vllm_codebase_skill_test.py --start-vllm
380
386
  ```
381
387
 
382
388
  This skill probe defaults the vLLM helper to `MAX_MODEL_LEN=32768`, which was
383
- the stable local Metal setting for the complete skill + `delphi_codebase` tool
384
- call. Override with `--max-model-len` when your machine has enough headroom.
385
-
386
- The probe requires opencode to load `delphi-codebase-navigator`, call
387
- `delphi_codebase`, and find `MegaProc02500`. It fails if opencode uses `bash`,
388
- `read`, `glob`, `grep`, `edit`, `write`, `task`, `webfetch`, or `todowrite`
389
- before the required evidence is complete.
389
+ the stable local Metal setting for the reduced codebase-agent prompt plus the
390
+ `delphi_codebase` tool call. Override with `--max-model-len` when your machine
391
+ has enough headroom.
392
+
393
+ The probe verifies that the `delphi-codebase-navigator` skill is installed, then
394
+ requires opencode to call `delphi_codebase`, find `MegaProc02500`, and read the
395
+ method body through the `implementation` layer. The vLLM test agent keeps
396
+ opencode's generic `skill` tool disabled so the model context stays focused on
397
+ the Delphi navigation tool. The probe fails if opencode uses `bash`, `read`,
398
+ `glob`, `grep`, `edit`, `write`, `task`, `webfetch`, or `todowrite` before the
399
+ required evidence is complete.
390
400
 
391
401
  ## Verification
392
402
 
@@ -101,9 +101,11 @@ def build_probe_command(
101
101
  ) -> list[str]:
102
102
  output_path = output or sandbox / "bootstrap_vllm_codebase_skill_probe.jsonl"
103
103
  prompt = (
104
- "Load the delphi-codebase-navigator skill. Use only the delphi_codebase tool to inspect the "
105
- "Delphi project. Find MegaProc02500 through the symbols layer and explain which project and unit "
106
- "contain it with file and line evidence."
104
+ "Use only the delphi_codebase tool to inspect the Delphi project. Do not write "
105
+ "any explanatory text before calling that tool. First call delphi_codebase with layer symbols, "
106
+ 'query "MegaProc02500", and format json. Then call delphi_codebase with layer implementation, '
107
+ 'query "MegaProc02500", and format markdown. After both tool calls, explain which project and '
108
+ "unit contain it with file and line evidence, and cite one statement from the method body."
107
109
  )
108
110
  return [
109
111
  str(python_executable),
@@ -115,9 +117,9 @@ def build_probe_command(
115
117
  "--agent",
116
118
  DEFAULT_AGENT,
117
119
  "--require-tool",
118
- "skill:delphi-codebase-navigator",
119
- "--require-tool",
120
120
  f"delphi_codebase:{DEFAULT_SYMBOL}",
121
+ "--require-tool",
122
+ "delphi_codebase:Value := Value + 40",
121
123
  "--forbid-tool",
122
124
  "bash",
123
125
  "--forbid-tool",
@@ -202,6 +204,12 @@ def main() -> int:
202
204
  wait_for_endpoint(args.base_url, args.ready_timeout, api_key=args.api_key)
203
205
  env = os.environ.copy()
204
206
  env["OPENCODE_EXPERIMENTAL_LSP_TOOL"] = "true"
207
+ npm_cache = sandbox / ".opencode" / ".npm-cache"
208
+ bun_cache = sandbox / ".opencode" / ".bun-cache"
209
+ npm_cache.mkdir(parents=True, exist_ok=True)
210
+ bun_cache.mkdir(parents=True, exist_ok=True)
211
+ env.setdefault("NPM_CONFIG_CACHE", str(npm_cache))
212
+ env.setdefault("BUN_INSTALL_CACHE_DIR", str(bun_cache))
205
213
  run(
206
214
  build_probe_command(
207
215
  root=ROOT,
@@ -51,6 +51,32 @@ def make_project(root: Path) -> None:
51
51
  )
52
52
 
53
53
 
54
+ def make_mega_project(root: Path, *, proc_count: int = 2500, statements_per_proc: int = 40) -> None:
55
+ lines = [
56
+ "unit Mega100kUnit;",
57
+ "",
58
+ "interface",
59
+ "",
60
+ "type",
61
+ " TMegaValue = Integer;",
62
+ "",
63
+ "implementation",
64
+ "",
65
+ ]
66
+ for index in range(1, proc_count + 1):
67
+ lines.append(f"procedure MegaProc{index:05d};")
68
+ lines.append("var")
69
+ lines.append(" Value: Integer;")
70
+ lines.append("begin")
71
+ lines.append(" Value := 0;")
72
+ for statement in range(1, statements_per_proc + 1):
73
+ lines.append(f" Value := Value + {statement};")
74
+ lines.append("end;")
75
+ lines.append("")
76
+ lines.append("end.")
77
+ write_text(root / "Mega100kUnit.pas", "\n".join(lines) + "\n")
78
+
79
+
54
80
  def test_builds_layered_markdown_without_exposing_routine_bodies(tmp_path: Path) -> None:
55
81
  make_project(tmp_path)
56
82
 
@@ -63,6 +89,52 @@ def test_builds_layered_markdown_without_exposing_routine_bodies(tmp_path: Path)
63
89
  assert "body must not be exposed" not in markdown
64
90
 
65
91
 
92
+ def test_implementation_layer_exposes_only_queried_method_body(tmp_path: Path) -> None:
93
+ make_project(tmp_path)
94
+
95
+ index = build_codebase_index(tmp_path)
96
+ markdown = render_layer(index, "implementation", query="TWorker.Run", output_format="markdown")
97
+
98
+ assert "TWorker.Run" in markdown
99
+ assert "procedure TWorker.Run;" in markdown
100
+ assert "body must not be exposed in the layer output" in markdown
101
+ assert "unit Worker;" not in markdown
102
+
103
+
104
+ def test_implementation_layer_exposes_class_declaration_and_method_bodies(tmp_path: Path) -> None:
105
+ make_project(tmp_path)
106
+
107
+ index = build_codebase_index(tmp_path)
108
+ payload = json.loads(render_layer(index, "implementation", query="TWorker", output_format="json"))
109
+
110
+ assert payload["layer"] == "implementation"
111
+ [item] = payload["items"]
112
+ fragment_kinds = {fragment["fragment_kind"] for fragment in item["fragments"]}
113
+ fragment_text = "\n".join(fragment["text"] for fragment in item["fragments"])
114
+ assert fragment_kinds == {"declaration", "implementation"}
115
+ assert "TWorker = class" in fragment_text
116
+ assert "procedure TWorker.Run;" in fragment_text
117
+ assert "body must not be exposed in the layer output" in fragment_text
118
+
119
+
120
+ def test_implementation_layer_slices_single_method_from_100k_line_file(tmp_path: Path) -> None:
121
+ make_mega_project(tmp_path)
122
+ source = (tmp_path / "Mega100kUnit.pas").read_text(encoding="utf-8")
123
+ assert source.count("\n") > 100_000
124
+
125
+ index = build_codebase_index(tmp_path)
126
+ payload = json.loads(render_layer(index, "implementation", query="MegaProc02500", output_format="json"))
127
+
128
+ [item] = payload["items"]
129
+ text = item["fragments"][0]["text"]
130
+ assert item["name"] == "MegaProc02500"
131
+ assert "procedure MegaProc02500;" in text
132
+ assert " Value := Value + 40;" in text
133
+ assert "procedure MegaProc02499;" not in text
134
+ assert "procedure MegaProc02498;" not in text
135
+ assert item["fragments"][0]["range"]["start_line"] > 100_000
136
+
137
+
66
138
  def test_default_layer_index_does_not_deep_parse_project_dependencies(tmp_path: Path, monkeypatch) -> None:
67
139
  make_project(tmp_path)
68
140
 
@@ -91,9 +163,9 @@ def test_agent_cli_outputs_symbol_layer_as_json(tmp_path: Path) -> None:
91
163
  "--root",
92
164
  str(tmp_path),
93
165
  "--layer",
94
- "symbols",
166
+ "implementation",
95
167
  "--query",
96
- "TWorker",
168
+ "TWorker.Run",
97
169
  "--format",
98
170
  "json",
99
171
  ],
@@ -103,8 +175,9 @@ def test_agent_cli_outputs_symbol_layer_as_json(tmp_path: Path) -> None:
103
175
  )
104
176
 
105
177
  payload = json.loads(completed.stdout)
106
- assert payload["layer"] == "symbols"
107
- assert any(item["name"] == "TWorker" for item in payload["items"])
178
+ assert payload["layer"] == "implementation"
179
+ assert any(item["name"] == "TWorker.Run" for item in payload["items"])
180
+ assert "procedure TWorker.Run;" in completed.stdout
108
181
 
109
182
 
110
183
  def test_opencode_install_writes_skill_and_custom_tool(tmp_path: Path) -> None:
@@ -133,6 +206,12 @@ def test_opencode_install_writes_skill_and_custom_tool(tmp_path: Path) -> None:
133
206
  tool_text = tool.read_text(encoding="utf-8")
134
207
  assert sys.executable in tool_text
135
208
  assert "delphiast.agent_cli" in tool_text
136
- assert "@opencode-ai/plugin" not in tool_text
209
+ assert '@opencode-ai/plugin' in tool_text
210
+ assert "tool.schema.enum" in tool_text
211
+ assert "tool.schema.string().optional()" in tool_text
212
+ assert 'args.format ?? "markdown"' in tool_text
213
+ assert '"implementation"' in tool_text
214
+ assert 'context.directory !== "/"' in tool_text
215
+ assert "process.cwd()" in tool_text
137
216
  assert "grep" not in tool_text.casefold()
138
217
  assert str(skill) in completed.stdout
@@ -31,6 +31,12 @@ def test_writes_codebase_skill_sandbox_with_project_paths(tmp_path: Path) -> Non
31
31
  config = (tmp_path / "opencode.json").read_text(encoding="utf-8")
32
32
  assert "vllm-delphi-codebase" in config
33
33
  assert '"delphi_codebase": true' in config
34
+ assert '"skill": false' in config
35
+ assert '"lsp": false' in config
36
+ assert '"delphi_codebase": "allow"' in config
37
+ assert '"skill": "deny"' in config
38
+ assert '"temperature": 0' in config
39
+ assert "When the user asks for a tool call" in config
34
40
  assert '"read": false' in config
35
41
 
36
42
 
@@ -42,7 +48,11 @@ def test_probe_command_requires_delphi_codebase_tool_and_forbids_raw_tools(tmp_p
42
48
  assert command[:2] == [str(python_exe), str(ROOT / "scripts" / "run_opencode_lsp_probe.py")]
43
49
  assert "vllm/ornith-lspctx" in command
44
50
  assert "vllm-delphi-codebase" in command
51
+ assert "skill:delphi-codebase-navigator" not in command
45
52
  assert "delphi_codebase:MegaProc02500" in command
53
+ assert "delphi_codebase:Value := Value + 40" in command
54
+ assert "Do not write any explanatory text before calling that tool" in command[-1]
55
+ assert "layer implementation" in command[-1]
46
56
  forbidden = [command[index + 1] for index, item in enumerate(command) if item == "--forbid-tool"]
47
57
  assert {"bash", "read", "glob", "grep", "edit", "write", "task", "webfetch", "todowrite"}.issubset(
48
58
  set(forbidden)
@@ -54,3 +64,5 @@ def test_skill_bootstrap_defaults_to_smaller_vllm_context_for_metal_stability()
54
64
 
55
65
  assert 'DEFAULT_MAX_MODEL_LEN = "32768"' in script
56
66
  assert 'os.environ["MAX_MODEL_LEN"] = args.max_model_len' in script
67
+ assert 'env.setdefault("NPM_CONFIG_CACHE"' in script
68
+ assert 'env.setdefault("BUN_INSTALL_CACHE_DIR"' in script
@@ -24,11 +24,11 @@ def test_lsp_console_script_dependencies_are_installed_by_default() -> None:
24
24
  assert '"lsprotocol>=2023.0.1"' in project
25
25
 
26
26
 
27
- def test_release_metadata_declares_1_1_0_and_windows_support() -> None:
27
+ def test_release_metadata_declares_1_1_1_and_windows_support() -> None:
28
28
  pyproject = (ROOT / 'pyproject.toml').read_text(encoding='utf-8')
29
29
  project = _section('project', pyproject)
30
30
 
31
- assert 'version = "1.1.0"' in project
31
+ assert 'version = "1.1.1"' in project
32
32
  assert '"Operating System :: OS Independent"' in project
33
33
  assert '"Operating System :: Microsoft :: Windows"' in project
34
34
  assert '"Operating System :: MacOS"' in project