python-delphi-lsp 2.1.0__tar.gz → 2.3.0__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 (154) hide show
  1. {python_delphi_lsp-2.1.0/python_delphi_lsp.egg-info → python_delphi_lsp-2.3.0}/PKG-INFO +30 -12
  2. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/README.md +28 -11
  3. python_delphi_lsp-2.3.0/delphi_lsp/_version.py +1 -0
  4. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/agent_cache.py +64 -6
  5. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/agent_context.py +721 -161
  6. python_delphi_lsp-2.3.0/delphi_lsp/agent_metrics.py +179 -0
  7. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/agent_protocol.py +4 -3
  8. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/agent_relations.py +300 -29
  9. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/agent_workspace.py +51 -3
  10. python_delphi_lsp-2.3.0/delphi_lsp/delphiast_lexer.py +284 -0
  11. python_delphi_lsp-2.3.0/delphi_lsp/delphiast_parser.py +1498 -0
  12. python_delphi_lsp-2.3.0/delphi_lsp/delphiast_tokens.py +41 -0
  13. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/metrics.py +80 -8
  14. python_delphi_lsp-2.3.0/delphi_lsp/navigation_cache.py +134 -0
  15. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/parallel_outline.py +49 -10
  16. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/parser.py +54 -4
  17. python_delphi_lsp-2.3.0/delphi_lsp/parser_backend.py +57 -0
  18. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/preprocessor.py +56 -20
  19. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/project_indexer.py +32 -1
  20. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/pyproject.toml +2 -1
  21. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0/python_delphi_lsp.egg-info}/PKG-INFO +30 -12
  22. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/python_delphi_lsp.egg-info/SOURCES.txt +11 -0
  23. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/python_delphi_lsp.egg-info/requires.txt +1 -0
  24. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_agent_cache.py +109 -6
  25. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_agent_context.py +391 -12
  26. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_agent_metrics.py +36 -0
  27. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_agent_protocol.py +31 -0
  28. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_agent_relations.py +55 -0
  29. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_agent_workspace.py +35 -0
  30. python_delphi_lsp-2.3.0/tests/test_delphiast_lexer.py +97 -0
  31. python_delphi_lsp-2.3.0/tests/test_delphiast_native_compatibility.py +364 -0
  32. python_delphi_lsp-2.3.0/tests/test_delphiast_parser.py +121 -0
  33. python_delphi_lsp-2.3.0/tests/test_delphiast_semantic_compatibility.py +74 -0
  34. python_delphi_lsp-2.3.0/tests/test_navigation_cache.py +43 -0
  35. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_package_metadata.py +3 -3
  36. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_parallel_outline.py +31 -3
  37. python_delphi_lsp-2.3.0/tests/test_parser_backends.py +135 -0
  38. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_preprocessor.py +57 -1
  39. python_delphi_lsp-2.1.0/delphi_lsp/_version.py +0 -1
  40. python_delphi_lsp-2.1.0/delphi_lsp/agent_metrics.py +0 -82
  41. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/.github/workflows/ci.yml +0 -0
  42. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/LICENSE +0 -0
  43. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/MANIFEST.in +0 -0
  44. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/__init__.py +0 -0
  45. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/agent_cli.py +0 -0
  46. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/agent_layers.py +0 -0
  47. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/agent_templates.py +0 -0
  48. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/binary.py +0 -0
  49. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/comment_builder.py +0 -0
  50. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/consts.py +0 -0
  51. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/grammar.py +0 -0
  52. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/lark_builder.py +0 -0
  53. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/lark_tokens.py +0 -0
  54. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/lsp_server.py +0 -0
  55. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/nodes.py +0 -0
  56. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/progress.py +0 -0
  57. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/project_discovery.py +0 -0
  58. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/semantic.py +0 -0
  59. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/semantic_builder.py +0 -0
  60. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/source_reader.py +0 -0
  61. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/workspace.py +0 -0
  62. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/writer.py +0 -0
  63. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/opencode.json +0 -0
  64. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/python_delphi_lsp.egg-info/dependency_links.txt +0 -0
  65. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/python_delphi_lsp.egg-info/entry_points.txt +0 -0
  66. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/python_delphi_lsp.egg-info/top_level.txt +0 -0
  67. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/audit_delphi_language_features.py +0 -0
  68. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/benchmark_github_corpus.py +0 -0
  69. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/benchmark_parallel_cache.py +0 -0
  70. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/bootstrap_vllm_codebase_skill_test.py +0 -0
  71. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/bootstrap_vllm_opencode_test.ps1 +0 -0
  72. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/bootstrap_vllm_opencode_test.py +0 -0
  73. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/bootstrap_vllm_opencode_test.sh +0 -0
  74. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/build_github_performance_corpus.py +0 -0
  75. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/check_ornith_cache.py +0 -0
  76. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/generate_progress_pdf.py +0 -0
  77. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/generate_release_evidence.py +0 -0
  78. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/ollama/ornith-lspctx.Modelfile +0 -0
  79. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/prepare_ornith_cache.py +0 -0
  80. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/run_opencode_lsp_probe.py +0 -0
  81. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/run_openrouter_github_e2e.py +0 -0
  82. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/start_ornith_vllm.sh +0 -0
  83. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/summarize_opencode_request_payloads.py +0 -0
  84. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/setup.cfg +0 -0
  85. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/corpora.performance.lock.json +0 -0
  86. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/DeprecatedOnConst.pas +0 -0
  87. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/VariantRecordFieldAttributes.pas +0 -0
  88. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/alignedrecords.pas +0 -0
  89. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/constset.pas +0 -0
  90. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/deprecatedtype.pas +0 -0
  91. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/dottedtypes.pas +0 -0
  92. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/endtoken.pas +0 -0
  93. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/experimentals.pas +0 -0
  94. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/externalfunction.pas +0 -0
  95. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/finalizationinitializationexports.pas +0 -0
  96. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/forwardoverloaded.pas +0 -0
  97. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/forwardwithoutsemicolon.pas +0 -0
  98. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/genericconstraints.pas +0 -0
  99. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/genericinterfacemethoddelegation.pas +0 -0
  100. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/implementsgenerictype.pas +0 -0
  101. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/include file2.inc +0 -0
  102. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/includefile.inc +0 -0
  103. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/includefile.pas +0 -0
  104. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/managedrecords.pas +0 -0
  105. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/messagemethod.pas +0 -0
  106. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/multiline.pas +0 -0
  107. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/nonalignedrecords.pas +0 -0
  108. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/numbers.pas +0 -0
  109. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/pointerchars.pas +0 -0
  110. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/properties.pas +0 -0
  111. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/strictvisibility.pas +0 -0
  112. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/tryexcept.pas +0 -0
  113. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/umlauts.pas +0 -0
  114. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/whitespacearoundifdefcondition.pas +0 -0
  115. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/library_demo.dpr +0 -0
  116. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/package_demo.dpk +0 -0
  117. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/program_demo.dpr +0 -0
  118. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_advanced.pas +0 -0
  119. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_attributes.pas +0 -0
  120. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_basic.pas +0 -0
  121. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_consumer.pas +0 -0
  122. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_generics.pas +0 -0
  123. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_inheritance.pas +0 -0
  124. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_math.pas +0 -0
  125. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_properties.pas +0 -0
  126. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_sections.pas +0 -0
  127. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_statements.pas +0 -0
  128. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_types.pas +0 -0
  129. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_unresolved.pas +0 -0
  130. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_with.pas +0 -0
  131. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_agent_codebase.py +0 -0
  132. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_agent_worker.py +0 -0
  133. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_bootstrap_vllm_codebase_skill.py +0 -0
  134. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_bootstrap_vllm_opencode.py +0 -0
  135. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_diagnostics.py +0 -0
  136. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_github_performance_corpus.py +0 -0
  137. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_language_feature_matrix.py +0 -0
  138. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_legacy_snippets.py +0 -0
  139. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_lsp_features.py +0 -0
  140. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_lsp_support.py +0 -0
  141. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_metrics.py +0 -0
  142. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_opencode_config.py +0 -0
  143. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_opencode_probe.py +0 -0
  144. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_opencode_request_payloads.py +0 -0
  145. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_openrouter_github_e2e.py +0 -0
  146. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_ornith_cache_prepare.py +0 -0
  147. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_parser.py +0 -0
  148. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_progress.py +0 -0
  149. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_project_discovery.py +0 -0
  150. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_project_indexer.py +0 -0
  151. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_release_evidence.py +0 -0
  152. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_semantic.py +0 -0
  153. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_vllm_start_script.py +0 -0
  154. {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-delphi-lsp
3
- Version: 2.1.0
3
+ Version: 2.3.0
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
@@ -24,6 +24,7 @@ License-File: LICENSE
24
24
  Requires-Dist: lark>=1.1.9
25
25
  Requires-Dist: pygls<2.0,>=1.3.0
26
26
  Requires-Dist: lsprotocol>=2023.0.1
27
+ Requires-Dist: watchfiles<2.0,>=1.2
27
28
  Provides-Extra: lsp
28
29
  Requires-Dist: pygls<2.0,>=1.3.0; extra == "lsp"
29
30
  Requires-Dist: lsprotocol>=2023.0.1; extra == "lsp"
@@ -41,7 +42,7 @@ Dynamic: license-file
41
42
 
42
43
  `python-delphi-lsp` parses Delphi/Object Pascal, builds semantic and project
43
44
  indexes, serves LSP, and provides bounded codebase navigation for agents.
44
- Version 2.1.0 is authored by Dark Light and supports Windows, macOS, and Linux.
45
+ Version 2.3.0 is authored by Dark Light and supports Windows, macOS, and Linux.
45
46
 
46
47
  ## Install and quick start
47
48
 
@@ -238,19 +239,35 @@ above 80 percent.
238
239
 
239
240
  Cold builds parse independent source units in short-lived processes created with
240
241
  the cross-platform `spawn` method. `--workers auto|N` defaults to `auto`.
241
- Automatic selection uses at most four worker processes, leaves one detected CPU
242
+ Automatic selection uses at most eight worker processes, leaves one detected CPU
242
243
  free, never exceeds the source task count, and—for the cache daemon—allows one
243
- worker per `128 MiB` of retained-cache budget. `view` and `index` use the same
244
- task, CPU, and four-worker caps without the cache-budget term. An explicit value
244
+ worker per `64 MiB` of retained-cache budget. `view` and `index` use the same
245
+ task, CPU, and eight-worker caps without the cache-budget term. An explicit value
245
246
  from 1 through 32 overrides the automatic CPU and memory caps but is still
246
247
  limited by the number of tasks.
247
248
 
248
- Worker processes exit before retained-cache accounting. Their models and source
249
- text are streamed into the parent, so transient worker memory is separate from
250
- the retained navigation structures and the existing 80-percent warning. If an
251
- automatic pool fails before accepting a result, one automatic serial fallback
252
- is attempted; explicit worker counts fail instead of silently changing the
253
- requested configuration.
249
+ Worker processes build compact, detached navigation shards and exit before
250
+ retained-cache accounting. Full semantic object graphs and source text are not
251
+ retained in the parent. Tokenized source documents are loaded only for source
252
+ evidence requests such as `inspect`, and a byte-bounded LRU evicts older
253
+ documents. The transient worker memory remains separate from retained navigation
254
+ structures and the existing 80-percent warning. Retained bytes are maintained
255
+ incrementally; requests never traverse the complete cache object graph merely
256
+ to measure it. If an automatic pool fails before accepting a result, one
257
+ automatic serial fallback is attempted; explicit worker counts fail instead of
258
+ silently changing the requested configuration.
259
+
260
+ Detached navigation shards are also stored as versioned, content-addressed JSON
261
+ under `.delphi-lsp/agent-cache/navigation-v1`. A restarted CLI or OpenCode cache
262
+ daemon reuses unchanged units without parsing them again. Source content,
263
+ conditional defines, or a shard-schema change produces a cache miss; malformed
264
+ or incompatible JSON is ignored and rebuilt. The disk cache contains no pickle
265
+ payloads and does not count against the retained-RAM budget.
266
+
267
+ Cache prewarming builds the navigation registry directly without constructing
268
+ an empty-query result, symbol cards, pagination, or JSON payloads. Up to sixteen
269
+ recent ranked queries are retained in a small LRU so alternating CLI and
270
+ OpenCode searches remain warm.
254
271
 
255
272
  `cache start` waits up to `--startup-timeout 120` seconds by default for a large
256
273
  workspace to prewarm. The timeout belongs to the starting client and does not
@@ -266,7 +283,8 @@ The daemon tracks a 30-minute idle timeout; idle state shows in JSON status (`ca
266
283
  Workspace state appears in status as `requests`, `warm_hits`, `rebuilds`, `invalidations`,
267
284
  `evictions`, and `cache_state`. Parallel prewarm status adds
268
285
  `workers_configured`, `workers_effective`, `parallel_files_completed`,
269
- `prewarm_seconds`, `parallel_seconds`, and `parallel_fallbacks`.
286
+ `prewarm_seconds`, `parallel_seconds`, `parallel_fallbacks`,
287
+ `navigation_disk_hits`, and `navigation_disk_misses`.
270
288
 
271
289
  Metadata is stored in `.delphi-lsp/agent-cache/daemon.json` with owner-only token and
272
290
  permissions (`daemon.json` mode 600 and parent 700). Do not copy or share this token
@@ -2,7 +2,7 @@
2
2
 
3
3
  `python-delphi-lsp` parses Delphi/Object Pascal, builds semantic and project
4
4
  indexes, serves LSP, and provides bounded codebase navigation for agents.
5
- Version 2.1.0 is authored by Dark Light and supports Windows, macOS, and Linux.
5
+ Version 2.3.0 is authored by Dark Light and supports Windows, macOS, and Linux.
6
6
 
7
7
  ## Install and quick start
8
8
 
@@ -199,19 +199,35 @@ above 80 percent.
199
199
 
200
200
  Cold builds parse independent source units in short-lived processes created with
201
201
  the cross-platform `spawn` method. `--workers auto|N` defaults to `auto`.
202
- Automatic selection uses at most four worker processes, leaves one detected CPU
202
+ Automatic selection uses at most eight worker processes, leaves one detected CPU
203
203
  free, never exceeds the source task count, and—for the cache daemon—allows one
204
- worker per `128 MiB` of retained-cache budget. `view` and `index` use the same
205
- task, CPU, and four-worker caps without the cache-budget term. An explicit value
204
+ worker per `64 MiB` of retained-cache budget. `view` and `index` use the same
205
+ task, CPU, and eight-worker caps without the cache-budget term. An explicit value
206
206
  from 1 through 32 overrides the automatic CPU and memory caps but is still
207
207
  limited by the number of tasks.
208
208
 
209
- Worker processes exit before retained-cache accounting. Their models and source
210
- text are streamed into the parent, so transient worker memory is separate from
211
- the retained navigation structures and the existing 80-percent warning. If an
212
- automatic pool fails before accepting a result, one automatic serial fallback
213
- is attempted; explicit worker counts fail instead of silently changing the
214
- requested configuration.
209
+ Worker processes build compact, detached navigation shards and exit before
210
+ retained-cache accounting. Full semantic object graphs and source text are not
211
+ retained in the parent. Tokenized source documents are loaded only for source
212
+ evidence requests such as `inspect`, and a byte-bounded LRU evicts older
213
+ documents. The transient worker memory remains separate from retained navigation
214
+ structures and the existing 80-percent warning. Retained bytes are maintained
215
+ incrementally; requests never traverse the complete cache object graph merely
216
+ to measure it. If an automatic pool fails before accepting a result, one
217
+ automatic serial fallback is attempted; explicit worker counts fail instead of
218
+ silently changing the requested configuration.
219
+
220
+ Detached navigation shards are also stored as versioned, content-addressed JSON
221
+ under `.delphi-lsp/agent-cache/navigation-v1`. A restarted CLI or OpenCode cache
222
+ daemon reuses unchanged units without parsing them again. Source content,
223
+ conditional defines, or a shard-schema change produces a cache miss; malformed
224
+ or incompatible JSON is ignored and rebuilt. The disk cache contains no pickle
225
+ payloads and does not count against the retained-RAM budget.
226
+
227
+ Cache prewarming builds the navigation registry directly without constructing
228
+ an empty-query result, symbol cards, pagination, or JSON payloads. Up to sixteen
229
+ recent ranked queries are retained in a small LRU so alternating CLI and
230
+ OpenCode searches remain warm.
215
231
 
216
232
  `cache start` waits up to `--startup-timeout 120` seconds by default for a large
217
233
  workspace to prewarm. The timeout belongs to the starting client and does not
@@ -227,7 +243,8 @@ The daemon tracks a 30-minute idle timeout; idle state shows in JSON status (`ca
227
243
  Workspace state appears in status as `requests`, `warm_hits`, `rebuilds`, `invalidations`,
228
244
  `evictions`, and `cache_state`. Parallel prewarm status adds
229
245
  `workers_configured`, `workers_effective`, `parallel_files_completed`,
230
- `prewarm_seconds`, `parallel_seconds`, and `parallel_fallbacks`.
246
+ `prewarm_seconds`, `parallel_seconds`, `parallel_fallbacks`,
247
+ `navigation_disk_hits`, and `navigation_disk_misses`.
231
248
 
232
249
  Metadata is stored in `.delphi-lsp/agent-cache/daemon.json` with owner-only token and
233
250
  permissions (`daemon.json` mode 600 and parent 700). Do not copy or share this token
@@ -0,0 +1 @@
1
+ __version__ = "2.3.0"
@@ -20,6 +20,8 @@ import threading
20
20
  import time
21
21
  from types import ModuleType
22
22
 
23
+ from watchfiles import watch
24
+
23
25
  from ._version import __version__
24
26
  from .agent_context import AgentContext
25
27
  from .agent_protocol import AgentProtocolError
@@ -32,10 +34,15 @@ DEFAULT_IDLE_TIMEOUT = 1800
32
34
  DEFAULT_STARTUP_TIMEOUT = 120.0
33
35
  _MAX_MESSAGE_BYTES = 1024 * 1024
34
36
  _CONNECTION_TIMEOUT = 2.0
37
+ _CLIENT_RESPONSE_TIMEOUT = 120.0
35
38
  _MEMORY_SIZE = re.compile(r"^(?P<count>[1-9][0-9]*)(?P<suffix>[KMG]?)$", re.IGNORECASE)
36
39
  _STARTUP_DIAGNOSTIC_BYTES = 16 * 1024
37
40
  _STARTUP_TOKEN_RE = re.compile(r"(?i)(token\b[^\n\r]*?:?\s*['\"]?[A-Za-z0-9_-]+['\"]?|\b[a-zA-Z0-9_-]{32,})")
38
41
  _START_LOCK_INCOMPLETE_GRACE_SECONDS = 1.0
42
+ _CACHE_REVISION_CHECK_INTERVAL_SECONDS = 3600.0
43
+ _WATCHED_SUFFIXES = frozenset(
44
+ {".pas", ".pp", ".inc", ".dpr", ".dpk", ".dproj", ".cfg"}
45
+ )
39
46
 
40
47
 
41
48
  def estimate_deep_size(value: object) -> int:
@@ -273,6 +280,18 @@ def _safe_metadata_path(root: str | Path, *, create: bool = False) -> Path:
273
280
  return result
274
281
 
275
282
 
283
+ def _safe_navigation_cache_path(root: str | Path, *, create: bool = False) -> Path:
284
+ parent = _safe_metadata_path(root, create=create).parent
285
+ result = parent / "navigation-v1"
286
+ if result.exists() and result.is_symlink():
287
+ raise CacheClientError("unsafe_metadata", "Navigation cache path is unsafe.")
288
+ if create:
289
+ result.mkdir(mode=0o700, exist_ok=True)
290
+ if os.name != "nt":
291
+ os.chmod(result, 0o700)
292
+ return result
293
+
294
+
276
295
  def _metadata_mapping(metadata: CacheMetadata) -> dict[str, object]:
277
296
  return {field.name: getattr(metadata, field.name) for field in fields(metadata)}
278
297
 
@@ -475,7 +494,7 @@ def _start_lock(root: str | Path, timeout: float):
475
494
  def _client_exchange(metadata: CacheMetadata, request: dict[str, object]) -> CacheClientResponse:
476
495
  try:
477
496
  with socket.create_connection(("127.0.0.1", metadata.port), timeout=2) as connection:
478
- connection.settimeout(3)
497
+ connection.settimeout(_CLIENT_RESPONSE_TIMEOUT)
479
498
  request_without_token = {key: value for key, value in request.items() if key != "token"}
480
499
  connection.sendall(json.dumps({"token": metadata.token, **request_without_token}, separators=(",", ":")).encode("utf-8") + b"\n")
481
500
  response = _read_line(connection)
@@ -541,6 +560,11 @@ class _CacheService:
541
560
  metadata.project_file or None,
542
561
  workers=metadata.workers,
543
562
  worker_memory_budget_bytes=metadata.max_memory_bytes,
563
+ revision_check_interval_seconds=_CACHE_REVISION_CHECK_INTERVAL_SECONDS,
564
+ navigation_cache_dir=_safe_navigation_cache_path(
565
+ metadata.root,
566
+ create=True,
567
+ ),
544
568
  )
545
569
  self.budget = CacheBudget(metadata.max_memory_bytes)
546
570
  self.stats = CacheStats()
@@ -556,15 +580,14 @@ class _CacheService:
556
580
  def prewarm(self) -> None:
557
581
  started = time.monotonic()
558
582
  try:
559
- self.context.handle({"action": "find", "query": "", "max_items": 1, "max_chars": 256})
560
- self.last_revision = self.context.workspace.workspace_revision
583
+ self.last_revision = self.context.prewarm_navigation()
561
584
  self.cache_state = "warm"
562
585
  except AgentProtocolError as error:
563
586
  if error.code != "project_required":
564
587
  raise
565
588
  self.cache_state = "ready"
566
589
  self.last_budget = self.budget.enforce(
567
- measure=lambda: estimate_deep_size(self.context.cache_roots()),
590
+ measure=lambda: self.context.estimated_cache_bytes,
568
591
  evict_auxiliary=self.context.evict_auxiliary_caches,
569
592
  evict_navigation=self.context.evict_navigation_caches,
570
593
  )
@@ -605,7 +628,7 @@ class _CacheService:
605
628
  if before != after:
606
629
  self.stats.invalidations += 1
607
630
  self.last_budget = self.budget.enforce(
608
- measure=lambda: estimate_deep_size(self.context.cache_roots()),
631
+ measure=lambda: self.context.estimated_cache_bytes,
609
632
  evict_auxiliary=self.context.evict_auxiliary_caches,
610
633
  evict_navigation=self.context.evict_navigation_caches,
611
634
  )
@@ -641,11 +664,34 @@ class _CacheService:
641
664
  "prewarm_seconds": self.prewarm_seconds,
642
665
  "parallel_seconds": self.context.parallel_stats.elapsed_seconds,
643
666
  "parallel_fallbacks": self.stats.parallel_fallbacks,
667
+ "navigation_disk_hits": self.context.navigation_disk_hits,
668
+ "navigation_disk_misses": self.context.navigation_disk_misses,
644
669
  "idle_timeout": self.metadata.idle_timeout, "idle_remaining": max(0.0, self.metadata.idle_timeout - idle),
645
- "workspace_revision": self.context.workspace.workspace_revision,
670
+ "workspace_revision": self.last_revision,
646
671
  }
647
672
 
648
673
 
674
+ def _watch_filter(_change: object, path: str) -> bool:
675
+ return Path(path).suffix.casefold() in _WATCHED_SUFFIXES
676
+
677
+
678
+ def _watch_workspace(service: _CacheService) -> None:
679
+ try:
680
+ for changes in watch(
681
+ service.metadata.root,
682
+ watch_filter=_watch_filter,
683
+ stop_event=service.shutdown,
684
+ debounce=50,
685
+ step=20,
686
+ recursive=True,
687
+ raise_interrupt=False,
688
+ ):
689
+ if changes:
690
+ service.context.invalidate_revision_cache()
691
+ except (OSError, RuntimeError):
692
+ service.context.invalidate_revision_cache()
693
+
694
+
649
695
  def _serve_connection(connection: socket.socket, service: _CacheService) -> None:
650
696
  try:
651
697
  line = _read_line(connection)
@@ -691,9 +737,17 @@ def run_cache_daemon(
691
737
  idle_timeout,
692
738
  time.time(),
693
739
  )
740
+ watcher: threading.Thread | None = None
694
741
  try:
695
742
  service = _CacheService(metadata)
696
743
  service.prewarm()
744
+ watcher = threading.Thread(
745
+ target=_watch_workspace,
746
+ args=(service,),
747
+ name="delphi-cache-watcher",
748
+ daemon=True,
749
+ )
750
+ watcher.start()
697
751
  _write_metadata(metadata)
698
752
  while not service.shutdown.is_set() and time.monotonic() - service.last_activity < idle_timeout:
699
753
  try:
@@ -704,6 +758,10 @@ def run_cache_daemon(
704
758
  connection.settimeout(_CONNECTION_TIMEOUT)
705
759
  _serve_connection(connection, service)
706
760
  finally:
761
+ if "service" in locals():
762
+ service.shutdown.set()
763
+ if watcher is not None:
764
+ watcher.join(timeout=2.0)
707
765
  listener.close()
708
766
  _remove_metadata_if_owned(metadata)
709
767