python-delphi-lsp 2.3.0__tar.gz → 2.3.2__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.3.0/python_delphi_lsp.egg-info → python_delphi_lsp-2.3.2}/PKG-INFO +38 -9
  2. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/README.md +37 -8
  3. python_delphi_lsp-2.3.2/delphi_lsp/_version.py +1 -0
  4. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_cache.py +144 -14
  5. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_cli.py +128 -14
  6. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_context.py +4 -2
  7. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_templates.py +1 -1
  8. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_workspace.py +23 -2
  9. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/project_discovery.py +94 -5
  10. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/pyproject.toml +1 -1
  11. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2/python_delphi_lsp.egg-info}/PKG-INFO +38 -9
  12. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/python_delphi_lsp.egg-info/SOURCES.txt +1 -0
  13. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_agent_cache.py +234 -1
  14. python_delphi_lsp-2.3.2/tests/test_agent_cli_errors.py +234 -0
  15. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_agent_codebase.py +1 -0
  16. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_agent_context.py +59 -12
  17. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_agent_worker.py +278 -6
  18. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_agent_workspace.py +57 -7
  19. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_package_metadata.py +3 -3
  20. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_project_discovery.py +93 -0
  21. python_delphi_lsp-2.3.0/delphi_lsp/_version.py +0 -1
  22. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/.github/workflows/ci.yml +0 -0
  23. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/LICENSE +0 -0
  24. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/MANIFEST.in +0 -0
  25. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/__init__.py +0 -0
  26. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_layers.py +0 -0
  27. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_metrics.py +0 -0
  28. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_protocol.py +0 -0
  29. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_relations.py +0 -0
  30. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/binary.py +0 -0
  31. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/comment_builder.py +0 -0
  32. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/consts.py +0 -0
  33. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/delphiast_lexer.py +0 -0
  34. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/delphiast_parser.py +0 -0
  35. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/delphiast_tokens.py +0 -0
  36. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/grammar.py +0 -0
  37. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/lark_builder.py +0 -0
  38. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/lark_tokens.py +0 -0
  39. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/lsp_server.py +0 -0
  40. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/metrics.py +0 -0
  41. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/navigation_cache.py +0 -0
  42. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/nodes.py +0 -0
  43. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/parallel_outline.py +0 -0
  44. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/parser.py +0 -0
  45. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/parser_backend.py +0 -0
  46. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/preprocessor.py +0 -0
  47. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/progress.py +0 -0
  48. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/project_indexer.py +0 -0
  49. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/semantic.py +0 -0
  50. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/semantic_builder.py +0 -0
  51. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/source_reader.py +0 -0
  52. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/workspace.py +0 -0
  53. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/delphi_lsp/writer.py +0 -0
  54. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/opencode.json +0 -0
  55. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/python_delphi_lsp.egg-info/dependency_links.txt +0 -0
  56. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/python_delphi_lsp.egg-info/entry_points.txt +0 -0
  57. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/python_delphi_lsp.egg-info/requires.txt +0 -0
  58. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/python_delphi_lsp.egg-info/top_level.txt +0 -0
  59. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/scripts/audit_delphi_language_features.py +0 -0
  60. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/scripts/benchmark_github_corpus.py +0 -0
  61. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/scripts/benchmark_parallel_cache.py +0 -0
  62. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/scripts/bootstrap_vllm_codebase_skill_test.py +0 -0
  63. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/scripts/bootstrap_vllm_opencode_test.ps1 +0 -0
  64. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/scripts/bootstrap_vllm_opencode_test.py +0 -0
  65. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/scripts/bootstrap_vllm_opencode_test.sh +0 -0
  66. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/scripts/build_github_performance_corpus.py +0 -0
  67. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/scripts/check_ornith_cache.py +0 -0
  68. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/scripts/generate_progress_pdf.py +0 -0
  69. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/scripts/generate_release_evidence.py +0 -0
  70. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/scripts/ollama/ornith-lspctx.Modelfile +0 -0
  71. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/scripts/prepare_ornith_cache.py +0 -0
  72. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/scripts/run_opencode_lsp_probe.py +0 -0
  73. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/scripts/run_openrouter_github_e2e.py +0 -0
  74. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/scripts/start_ornith_vllm.sh +0 -0
  75. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/scripts/summarize_opencode_request_payloads.py +0 -0
  76. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/setup.cfg +0 -0
  77. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/corpora.performance.lock.json +0 -0
  78. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/DeprecatedOnConst.pas +0 -0
  79. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/VariantRecordFieldAttributes.pas +0 -0
  80. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/alignedrecords.pas +0 -0
  81. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/constset.pas +0 -0
  82. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/deprecatedtype.pas +0 -0
  83. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/dottedtypes.pas +0 -0
  84. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/endtoken.pas +0 -0
  85. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/experimentals.pas +0 -0
  86. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/externalfunction.pas +0 -0
  87. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/finalizationinitializationexports.pas +0 -0
  88. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/forwardoverloaded.pas +0 -0
  89. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/forwardwithoutsemicolon.pas +0 -0
  90. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/genericconstraints.pas +0 -0
  91. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/genericinterfacemethoddelegation.pas +0 -0
  92. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/implementsgenerictype.pas +0 -0
  93. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/include file2.inc +0 -0
  94. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/includefile.inc +0 -0
  95. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/includefile.pas +0 -0
  96. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/managedrecords.pas +0 -0
  97. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/messagemethod.pas +0 -0
  98. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/multiline.pas +0 -0
  99. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/nonalignedrecords.pas +0 -0
  100. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/numbers.pas +0 -0
  101. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/pointerchars.pas +0 -0
  102. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/properties.pas +0 -0
  103. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/strictvisibility.pas +0 -0
  104. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/tryexcept.pas +0 -0
  105. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/umlauts.pas +0 -0
  106. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/whitespacearoundifdefcondition.pas +0 -0
  107. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/library_demo.dpr +0 -0
  108. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/package_demo.dpk +0 -0
  109. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/program_demo.dpr +0 -0
  110. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_advanced.pas +0 -0
  111. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_attributes.pas +0 -0
  112. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_basic.pas +0 -0
  113. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_consumer.pas +0 -0
  114. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_generics.pas +0 -0
  115. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_inheritance.pas +0 -0
  116. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_math.pas +0 -0
  117. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_properties.pas +0 -0
  118. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_sections.pas +0 -0
  119. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_statements.pas +0 -0
  120. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_types.pas +0 -0
  121. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_unresolved.pas +0 -0
  122. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_with.pas +0 -0
  123. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_agent_metrics.py +0 -0
  124. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_agent_protocol.py +0 -0
  125. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_agent_relations.py +0 -0
  126. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_bootstrap_vllm_codebase_skill.py +0 -0
  127. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_bootstrap_vllm_opencode.py +0 -0
  128. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_delphiast_lexer.py +0 -0
  129. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_delphiast_native_compatibility.py +0 -0
  130. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_delphiast_parser.py +0 -0
  131. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_delphiast_semantic_compatibility.py +0 -0
  132. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_diagnostics.py +0 -0
  133. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_github_performance_corpus.py +0 -0
  134. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_language_feature_matrix.py +0 -0
  135. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_legacy_snippets.py +0 -0
  136. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_lsp_features.py +0 -0
  137. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_lsp_support.py +0 -0
  138. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_metrics.py +0 -0
  139. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_navigation_cache.py +0 -0
  140. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_opencode_config.py +0 -0
  141. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_opencode_probe.py +0 -0
  142. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_opencode_request_payloads.py +0 -0
  143. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_openrouter_github_e2e.py +0 -0
  144. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_ornith_cache_prepare.py +0 -0
  145. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_parallel_outline.py +0 -0
  146. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_parser.py +0 -0
  147. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_parser_backends.py +0 -0
  148. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_preprocessor.py +0 -0
  149. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_progress.py +0 -0
  150. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_project_indexer.py +0 -0
  151. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_release_evidence.py +0 -0
  152. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_semantic.py +0 -0
  153. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/tests/test_vllm_start_script.py +0 -0
  154. {python_delphi_lsp-2.3.0 → python_delphi_lsp-2.3.2}/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.3.0
3
+ Version: 2.3.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
@@ -42,7 +42,7 @@ Dynamic: license-file
42
42
 
43
43
  `python-delphi-lsp` parses Delphi/Object Pascal, builds semantic and project
44
44
  indexes, serves LSP, and provides bounded codebase navigation for agents.
45
- Version 2.3.0 is authored by Dark Light and supports Windows, macOS, and Linux.
45
+ Version 2.3.2 is authored by Dark Light and supports Windows, macOS, and Linux.
46
46
 
47
47
  ## Install and quick start
48
48
 
@@ -166,7 +166,8 @@ available without returning the complete file as agent context.
166
166
  Auto-discovery reads `.dpr`, `.dpk`, `.dproj`, `.cfg`, and `.dof` files.
167
167
  Its resolution order is:
168
168
 
169
- 1. An explicit project selection takes precedence.
169
+ 1. An explicit `.dproj`, `.dpr`, or `.dpk` selection takes precedence. An
170
+ explicit `.dproj` resolves its `MainSource`.
170
171
  2. Otherwise, `.dpr` and `.dpk` candidates are considered.
171
172
  3. `MainSource` in a `.dproj` contributes its entry project.
172
173
  4. A selected entry associates same-stem `.dproj`, `.cfg`, and `.dof`.
@@ -175,11 +176,13 @@ Its resolution order is:
175
176
  6. Direct `Unit in 'path/Unit.pas'` references contribute their parent
176
177
  directory to unit search paths.
177
178
 
178
- A single discovered project is selected automatically. With no project entry,
179
- the server uses a synthetic workspace of supported sources. Scans skip build
180
- and cache directories such as `build`, `dist`, environments, VCS folders,
181
- `node_modules`, and tool caches. Missing paths and invalid metadata become
182
- problems; paths are not guessed.
179
+ A single discovered project is selected automatically. With multiple project
180
+ entries, a synthetic repository workspace containing every supported source is
181
+ selected automatically while the concrete projects remain available. With no
182
+ project entry, the same workspace model is used. Scans skip build and cache
183
+ directories such as `build`, `dist`, environments, VCS folders, `node_modules`,
184
+ and tool caches. Missing paths and invalid metadata become problems; paths are
185
+ not guessed.
183
186
 
184
187
  ## Agent CLI and Interface/Protocol v2
185
188
 
@@ -197,7 +200,7 @@ delphi-lsp-agent view --root PATH [--project-file FILE] --layer LAYER
197
200
  delphi-lsp-agent index --root PATH [--project-file FILE] [--out FILE]
198
201
  [--workers auto|N]
199
202
  delphi-lsp-agent query --root PATH ACTION [VALUE]
200
- [--project-id FILE] [--detail summary|declaration|members|context|body|implementations]
203
+ [--project-id ID] [--detail summary|declaration|members|context|body|implementations]
201
204
  [--relation references|callers|callees|uses|used_by|inherits|implements]
202
205
  [--cursor TEXT] [--max-items INT] [--max-chars INT]
203
206
  delphi-lsp-agent skill install [--target PATH] [--force]
@@ -229,6 +232,32 @@ delphi-lsp-agent query --root PATH metrics UNIT_QUERY
229
232
  delphi-lsp-agent cache status --root PATH --format json
230
233
  ```
231
234
 
235
+ The repository root is sufficient even when it contains many projects:
236
+
237
+ ```bash
238
+ delphi-lsp-agent cache start --root PATH
239
+ delphi-lsp-agent query --root PATH find TCustomer
240
+ ```
241
+
242
+ This prewarms one bounded repository navigation index and requires no project
243
+ selection. A `.dproj` is optional. Pass one when its project-specific compiler
244
+ settings and `MainSource` should define the index:
245
+
246
+ ```bash
247
+ delphi-lsp-agent cache start --root PATH --project-file relative/path/Main.dproj
248
+ ```
249
+
250
+ An explicit `.dpr` or `.dpk` remains supported. To switch an already running
251
+ repository cache to one of its concrete project views, list the projects and
252
+ focus one by its returned `project_id`:
253
+
254
+ ```bash
255
+ delphi-lsp-agent query --root PATH open
256
+ delphi-lsp-agent query --root PATH focus --project-id PROJECT_ID
257
+ ```
258
+
259
+ Selecting a project this way also prewarms its navigation cache.
260
+
232
261
  `inspect` uses the currently focused target, so call `focus TARGET_ID` before
233
262
  `inspect` unless a previous request already selected it.
234
263
 
@@ -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.3.0 is authored by Dark Light and supports Windows, macOS, and Linux.
5
+ Version 2.3.2 is authored by Dark Light and supports Windows, macOS, and Linux.
6
6
 
7
7
  ## Install and quick start
8
8
 
@@ -126,7 +126,8 @@ available without returning the complete file as agent context.
126
126
  Auto-discovery reads `.dpr`, `.dpk`, `.dproj`, `.cfg`, and `.dof` files.
127
127
  Its resolution order is:
128
128
 
129
- 1. An explicit project selection takes precedence.
129
+ 1. An explicit `.dproj`, `.dpr`, or `.dpk` selection takes precedence. An
130
+ explicit `.dproj` resolves its `MainSource`.
130
131
  2. Otherwise, `.dpr` and `.dpk` candidates are considered.
131
132
  3. `MainSource` in a `.dproj` contributes its entry project.
132
133
  4. A selected entry associates same-stem `.dproj`, `.cfg`, and `.dof`.
@@ -135,11 +136,13 @@ Its resolution order is:
135
136
  6. Direct `Unit in 'path/Unit.pas'` references contribute their parent
136
137
  directory to unit search paths.
137
138
 
138
- A single discovered project is selected automatically. With no project entry,
139
- the server uses a synthetic workspace of supported sources. Scans skip build
140
- and cache directories such as `build`, `dist`, environments, VCS folders,
141
- `node_modules`, and tool caches. Missing paths and invalid metadata become
142
- problems; paths are not guessed.
139
+ A single discovered project is selected automatically. With multiple project
140
+ entries, a synthetic repository workspace containing every supported source is
141
+ selected automatically while the concrete projects remain available. With no
142
+ project entry, the same workspace model is used. Scans skip build and cache
143
+ directories such as `build`, `dist`, environments, VCS folders, `node_modules`,
144
+ and tool caches. Missing paths and invalid metadata become problems; paths are
145
+ not guessed.
143
146
 
144
147
  ## Agent CLI and Interface/Protocol v2
145
148
 
@@ -157,7 +160,7 @@ delphi-lsp-agent view --root PATH [--project-file FILE] --layer LAYER
157
160
  delphi-lsp-agent index --root PATH [--project-file FILE] [--out FILE]
158
161
  [--workers auto|N]
159
162
  delphi-lsp-agent query --root PATH ACTION [VALUE]
160
- [--project-id FILE] [--detail summary|declaration|members|context|body|implementations]
163
+ [--project-id ID] [--detail summary|declaration|members|context|body|implementations]
161
164
  [--relation references|callers|callees|uses|used_by|inherits|implements]
162
165
  [--cursor TEXT] [--max-items INT] [--max-chars INT]
163
166
  delphi-lsp-agent skill install [--target PATH] [--force]
@@ -189,6 +192,32 @@ delphi-lsp-agent query --root PATH metrics UNIT_QUERY
189
192
  delphi-lsp-agent cache status --root PATH --format json
190
193
  ```
191
194
 
195
+ The repository root is sufficient even when it contains many projects:
196
+
197
+ ```bash
198
+ delphi-lsp-agent cache start --root PATH
199
+ delphi-lsp-agent query --root PATH find TCustomer
200
+ ```
201
+
202
+ This prewarms one bounded repository navigation index and requires no project
203
+ selection. A `.dproj` is optional. Pass one when its project-specific compiler
204
+ settings and `MainSource` should define the index:
205
+
206
+ ```bash
207
+ delphi-lsp-agent cache start --root PATH --project-file relative/path/Main.dproj
208
+ ```
209
+
210
+ An explicit `.dpr` or `.dpk` remains supported. To switch an already running
211
+ repository cache to one of its concrete project views, list the projects and
212
+ focus one by its returned `project_id`:
213
+
214
+ ```bash
215
+ delphi-lsp-agent query --root PATH open
216
+ delphi-lsp-agent query --root PATH focus --project-id PROJECT_ID
217
+ ```
218
+
219
+ Selecting a project this way also prewarms its navigation cache.
220
+
192
221
  `inspect` uses the currently focused target, so call `focus TARGET_ID` before
193
222
  `inspect` unless a previous request already selected it.
194
223
 
@@ -0,0 +1 @@
1
+ __version__ = "2.3.2"
@@ -45,6 +45,88 @@ _WATCHED_SUFFIXES = frozenset(
45
45
  )
46
46
 
47
47
 
48
+ def current_process_rss_bytes() -> int:
49
+ """Return the current resident set size, or zero when unavailable."""
50
+ try:
51
+ if sys.platform == "darwin":
52
+ return _darwin_process_rss_bytes()
53
+ if sys.platform.startswith("linux"):
54
+ statm = Path("/proc/self/statm").read_text(encoding="ascii").split()
55
+ return int(statm[1]) * int(os.sysconf("SC_PAGE_SIZE"))
56
+ if os.name == "nt":
57
+ return _windows_process_rss_bytes()
58
+ except Exception:
59
+ pass
60
+ return 0
61
+
62
+
63
+ def _darwin_process_rss_bytes() -> int:
64
+ import ctypes
65
+
66
+ class TimeValue(ctypes.Structure):
67
+ _fields_ = [("seconds", ctypes.c_int), ("microseconds", ctypes.c_int)]
68
+
69
+ class MachTaskBasicInfo(ctypes.Structure):
70
+ _fields_ = [
71
+ ("virtual_size", ctypes.c_uint64),
72
+ ("resident_size", ctypes.c_uint64),
73
+ ("resident_size_max", ctypes.c_uint64),
74
+ ("user_time", TimeValue),
75
+ ("system_time", TimeValue),
76
+ ("policy", ctypes.c_int),
77
+ ("suspend_count", ctypes.c_int),
78
+ ]
79
+
80
+ library = ctypes.CDLL(None)
81
+ library.mach_task_self.restype = ctypes.c_uint
82
+ library.task_info.argtypes = (
83
+ ctypes.c_uint,
84
+ ctypes.c_int,
85
+ ctypes.c_void_p,
86
+ ctypes.POINTER(ctypes.c_uint),
87
+ )
88
+ library.task_info.restype = ctypes.c_int
89
+ info = MachTaskBasicInfo()
90
+ count = ctypes.c_uint(ctypes.sizeof(info) // ctypes.sizeof(ctypes.c_int))
91
+ result = library.task_info(
92
+ library.mach_task_self(),
93
+ 20,
94
+ ctypes.byref(info),
95
+ ctypes.byref(count),
96
+ )
97
+ return int(info.resident_size) if result == 0 else 0
98
+
99
+
100
+ def _windows_process_rss_bytes() -> int:
101
+ import ctypes
102
+ from ctypes import wintypes
103
+
104
+ class ProcessMemoryCounters(ctypes.Structure):
105
+ _fields_ = [
106
+ ("cb", wintypes.DWORD),
107
+ ("PageFaultCount", wintypes.DWORD),
108
+ ("PeakWorkingSetSize", ctypes.c_size_t),
109
+ ("WorkingSetSize", ctypes.c_size_t),
110
+ ("QuotaPeakPagedPoolUsage", ctypes.c_size_t),
111
+ ("QuotaPagedPoolUsage", ctypes.c_size_t),
112
+ ("QuotaPeakNonPagedPoolUsage", ctypes.c_size_t),
113
+ ("QuotaNonPagedPoolUsage", ctypes.c_size_t),
114
+ ("PagefileUsage", ctypes.c_size_t),
115
+ ("PeakPagefileUsage", ctypes.c_size_t),
116
+ ]
117
+
118
+ counters = ProcessMemoryCounters()
119
+ counters.cb = ctypes.sizeof(counters)
120
+ process = ctypes.windll.kernel32.GetCurrentProcess()
121
+ if not ctypes.windll.psapi.GetProcessMemoryInfo(
122
+ process,
123
+ ctypes.byref(counters),
124
+ counters.cb,
125
+ ):
126
+ return 0
127
+ return int(counters.WorkingSetSize)
128
+
129
+
48
130
  def estimate_deep_size(value: object) -> int:
49
131
  """Estimate the memory retained by an owned object graph.
50
132
 
@@ -292,6 +374,11 @@ def _safe_navigation_cache_path(root: str | Path, *, create: bool = False) -> Pa
292
374
  return result
293
375
 
294
376
 
377
+ def navigation_cache_path(root: str | Path, *, create: bool = False) -> Path:
378
+ """Return the validated persistent navigation-cache directory."""
379
+ return _safe_navigation_cache_path(root, create=create)
380
+
381
+
295
382
  def _metadata_mapping(metadata: CacheMetadata) -> dict[str, object]:
296
383
  return {field.name: getattr(metadata, field.name) for field in fields(metadata)}
297
384
 
@@ -552,16 +639,32 @@ def _read_startup_tail(diagnostics: object, *, max_bytes: int = _STARTUP_DIAGNOS
552
639
  return b""
553
640
 
554
641
 
642
+ def _daemon_process_options() -> dict[str, object]:
643
+ options: dict[str, object] = {
644
+ "stdin": subprocess.DEVNULL,
645
+ "stdout": subprocess.DEVNULL,
646
+ }
647
+ if os.name == "nt":
648
+ options["creationflags"] = (
649
+ subprocess.CREATE_NO_WINDOW
650
+ | subprocess.CREATE_NEW_PROCESS_GROUP
651
+ )
652
+ else:
653
+ options["start_new_session"] = True
654
+ return options
655
+
656
+
555
657
  class _CacheService:
556
658
  def __init__(self, metadata: CacheMetadata) -> None:
557
659
  self.metadata = metadata
660
+ self._rss_baseline_bytes = current_process_rss_bytes()
558
661
  self.context = AgentContext.open(
559
662
  metadata.root,
560
663
  metadata.project_file or None,
561
664
  workers=metadata.workers,
562
665
  worker_memory_budget_bytes=metadata.max_memory_bytes,
563
666
  revision_check_interval_seconds=_CACHE_REVISION_CHECK_INTERVAL_SECONDS,
564
- navigation_cache_dir=_safe_navigation_cache_path(
667
+ navigation_cache_dir=navigation_cache_path(
565
668
  metadata.root,
566
669
  create=True,
567
670
  ),
@@ -577,6 +680,15 @@ class _CacheService:
577
680
  self.prewarm_seconds = 0.0
578
681
  self.shutdown = threading.Event()
579
682
 
683
+ def _measure_retained_bytes(self) -> int:
684
+ process_rss = current_process_rss_bytes()
685
+ process_growth = (
686
+ max(0, process_rss - self._rss_baseline_bytes)
687
+ if process_rss > 0 and self._rss_baseline_bytes > 0
688
+ else 0
689
+ )
690
+ return max(self.context.estimated_cache_bytes, process_growth)
691
+
580
692
  def prewarm(self) -> None:
581
693
  started = time.monotonic()
582
694
  try:
@@ -587,7 +699,7 @@ class _CacheService:
587
699
  raise
588
700
  self.cache_state = "ready"
589
701
  self.last_budget = self.budget.enforce(
590
- measure=lambda: self.context.estimated_cache_bytes,
702
+ measure=self._measure_retained_bytes,
591
703
  evict_auxiliary=self.context.evict_auxiliary_caches,
592
704
  evict_navigation=self.context.evict_navigation_caches,
593
705
  )
@@ -628,7 +740,7 @@ class _CacheService:
628
740
  if before != after:
629
741
  self.stats.invalidations += 1
630
742
  self.last_budget = self.budget.enforce(
631
- measure=lambda: self.context.estimated_cache_bytes,
743
+ measure=self._measure_retained_bytes,
632
744
  evict_auxiliary=self.context.evict_auxiliary_caches,
633
745
  evict_navigation=self.context.evict_navigation_caches,
634
746
  )
@@ -675,21 +787,34 @@ def _watch_filter(_change: object, path: str) -> bool:
675
787
  return Path(path).suffix.casefold() in _WATCHED_SUFFIXES
676
788
 
677
789
 
678
- def _watch_workspace(service: _CacheService) -> None:
790
+ def watch_workspace_changes(
791
+ root: str | Path,
792
+ *,
793
+ stop_event: threading.Event,
794
+ on_change: Callable[[], None],
795
+ ) -> None:
679
796
  try:
680
797
  for changes in watch(
681
- service.metadata.root,
798
+ root,
682
799
  watch_filter=_watch_filter,
683
- stop_event=service.shutdown,
800
+ stop_event=stop_event,
684
801
  debounce=50,
685
802
  step=20,
686
803
  recursive=True,
687
804
  raise_interrupt=False,
688
805
  ):
689
806
  if changes:
690
- service.context.invalidate_revision_cache()
807
+ on_change()
691
808
  except (OSError, RuntimeError):
692
- service.context.invalidate_revision_cache()
809
+ on_change()
810
+
811
+
812
+ def _watch_workspace(service: _CacheService) -> None:
813
+ watch_workspace_changes(
814
+ service.metadata.root,
815
+ stop_event=service.shutdown,
816
+ on_change=service.context.invalidate_revision_cache,
817
+ )
693
818
 
694
819
 
695
820
  def _serve_connection(connection: socket.socket, service: _CacheService) -> None:
@@ -811,11 +936,7 @@ def _start_cache_unlocked(
811
936
  ]
812
937
  if project:
813
938
  command.extend(("--project-file", project))
814
- options: dict[str, object] = {"stdin": subprocess.DEVNULL, "stdout": subprocess.DEVNULL}
815
- if os.name == "nt":
816
- options["creationflags"] = subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
817
- else:
818
- options["start_new_session"] = True
939
+ options = _daemon_process_options()
819
940
  with tempfile.TemporaryFile() as diagnostics:
820
941
  options["stderr"] = diagnostics
821
942
  process = subprocess.Popen(command, **options)
@@ -867,6 +988,8 @@ def start_cache(
867
988
  ) -> CacheMetadata:
868
989
  if type(workers) is not int or not 0 <= workers <= 32:
869
990
  raise ValueError("workers must be auto or an integer from 1 through 32.")
991
+ if type(idle_timeout) is not int or idle_timeout <= 0:
992
+ raise ValueError("idle_timeout must be greater than zero.")
870
993
  if not math.isfinite(startup_timeout) or startup_timeout <= 0:
871
994
  raise ValueError("startup_timeout must be greater than zero.")
872
995
  with _start_lock(root, startup_timeout):
@@ -930,7 +1053,7 @@ def main(argv: list[str] | None = None) -> int:
930
1053
  serve.add_argument("--project-file", default="")
931
1054
  serve.add_argument("--max-memory", type=int, default=DEFAULT_MAX_MEMORY_BYTES)
932
1055
  serve.add_argument("--workers", type=int, default=0)
933
- serve.add_argument("--idle-timeout", type=int, default=DEFAULT_IDLE_TIMEOUT)
1056
+ serve.add_argument("--idle-timeout", type=_positive_integer, default=DEFAULT_IDLE_TIMEOUT)
934
1057
  args = parser.parse_args(argv)
935
1058
  if args.command == "serve":
936
1059
  run_cache_daemon(
@@ -943,5 +1066,12 @@ def main(argv: list[str] | None = None) -> int:
943
1066
  return 0
944
1067
 
945
1068
 
1069
+ def _positive_integer(value: str) -> int:
1070
+ parsed = int(value)
1071
+ if parsed <= 0:
1072
+ raise argparse.ArgumentTypeError("value must be greater than zero")
1073
+ return parsed
1074
+
1075
+
946
1076
  if __name__ == "__main__":
947
1077
  raise SystemExit(main())