python-delphi-lsp 2.3.1__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.1/python_delphi_lsp.egg-info → python_delphi_lsp-2.3.2}/PKG-INFO +38 -9
  2. {python_delphi_lsp-2.3.1 → 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.1 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_cache.py +16 -5
  5. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_cli.py +8 -1
  6. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_context.py +3 -1
  7. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_templates.py +1 -1
  8. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_workspace.py +23 -2
  9. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/project_discovery.py +89 -4
  10. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/pyproject.toml +1 -1
  11. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2/python_delphi_lsp.egg-info}/PKG-INFO +38 -9
  12. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_agent_cache.py +108 -0
  13. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_agent_cli_errors.py +12 -0
  14. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_agent_codebase.py +1 -0
  15. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_agent_context.py +22 -11
  16. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_agent_worker.py +50 -0
  17. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_agent_workspace.py +57 -7
  18. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_package_metadata.py +3 -3
  19. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_project_discovery.py +73 -0
  20. python_delphi_lsp-2.3.1/delphi_lsp/_version.py +0 -1
  21. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/.github/workflows/ci.yml +0 -0
  22. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/LICENSE +0 -0
  23. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/MANIFEST.in +0 -0
  24. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/__init__.py +0 -0
  25. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_layers.py +0 -0
  26. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_metrics.py +0 -0
  27. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_protocol.py +0 -0
  28. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/agent_relations.py +0 -0
  29. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/binary.py +0 -0
  30. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/comment_builder.py +0 -0
  31. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/consts.py +0 -0
  32. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/delphiast_lexer.py +0 -0
  33. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/delphiast_parser.py +0 -0
  34. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/delphiast_tokens.py +0 -0
  35. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/grammar.py +0 -0
  36. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/lark_builder.py +0 -0
  37. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/lark_tokens.py +0 -0
  38. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/lsp_server.py +0 -0
  39. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/metrics.py +0 -0
  40. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/navigation_cache.py +0 -0
  41. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/nodes.py +0 -0
  42. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/parallel_outline.py +0 -0
  43. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/parser.py +0 -0
  44. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/parser_backend.py +0 -0
  45. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/preprocessor.py +0 -0
  46. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/progress.py +0 -0
  47. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/project_indexer.py +0 -0
  48. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/semantic.py +0 -0
  49. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/semantic_builder.py +0 -0
  50. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/source_reader.py +0 -0
  51. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/workspace.py +0 -0
  52. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/delphi_lsp/writer.py +0 -0
  53. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/opencode.json +0 -0
  54. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/python_delphi_lsp.egg-info/SOURCES.txt +0 -0
  55. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/python_delphi_lsp.egg-info/dependency_links.txt +0 -0
  56. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/python_delphi_lsp.egg-info/entry_points.txt +0 -0
  57. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/python_delphi_lsp.egg-info/requires.txt +0 -0
  58. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/python_delphi_lsp.egg-info/top_level.txt +0 -0
  59. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/scripts/audit_delphi_language_features.py +0 -0
  60. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/scripts/benchmark_github_corpus.py +0 -0
  61. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/scripts/benchmark_parallel_cache.py +0 -0
  62. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/scripts/bootstrap_vllm_codebase_skill_test.py +0 -0
  63. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/scripts/bootstrap_vllm_opencode_test.ps1 +0 -0
  64. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/scripts/bootstrap_vllm_opencode_test.py +0 -0
  65. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/scripts/bootstrap_vllm_opencode_test.sh +0 -0
  66. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/scripts/build_github_performance_corpus.py +0 -0
  67. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/scripts/check_ornith_cache.py +0 -0
  68. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/scripts/generate_progress_pdf.py +0 -0
  69. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/scripts/generate_release_evidence.py +0 -0
  70. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/scripts/ollama/ornith-lspctx.Modelfile +0 -0
  71. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/scripts/prepare_ornith_cache.py +0 -0
  72. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/scripts/run_opencode_lsp_probe.py +0 -0
  73. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/scripts/run_openrouter_github_e2e.py +0 -0
  74. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/scripts/start_ornith_vllm.sh +0 -0
  75. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/scripts/summarize_opencode_request_payloads.py +0 -0
  76. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/setup.cfg +0 -0
  77. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/corpora.performance.lock.json +0 -0
  78. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/DeprecatedOnConst.pas +0 -0
  79. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/VariantRecordFieldAttributes.pas +0 -0
  80. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/alignedrecords.pas +0 -0
  81. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/constset.pas +0 -0
  82. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/deprecatedtype.pas +0 -0
  83. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/dottedtypes.pas +0 -0
  84. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/endtoken.pas +0 -0
  85. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/experimentals.pas +0 -0
  86. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/externalfunction.pas +0 -0
  87. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/finalizationinitializationexports.pas +0 -0
  88. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/forwardoverloaded.pas +0 -0
  89. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/forwardwithoutsemicolon.pas +0 -0
  90. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/genericconstraints.pas +0 -0
  91. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/genericinterfacemethoddelegation.pas +0 -0
  92. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/implementsgenerictype.pas +0 -0
  93. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/include file2.inc +0 -0
  94. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/includefile.inc +0 -0
  95. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/includefile.pas +0 -0
  96. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/managedrecords.pas +0 -0
  97. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/messagemethod.pas +0 -0
  98. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/multiline.pas +0 -0
  99. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/nonalignedrecords.pas +0 -0
  100. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/numbers.pas +0 -0
  101. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/pointerchars.pas +0 -0
  102. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/properties.pas +0 -0
  103. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/strictvisibility.pas +0 -0
  104. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/tryexcept.pas +0 -0
  105. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/umlauts.pas +0 -0
  106. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/legacy_snippets/whitespacearoundifdefcondition.pas +0 -0
  107. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/library_demo.dpr +0 -0
  108. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/package_demo.dpk +0 -0
  109. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/program_demo.dpr +0 -0
  110. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_advanced.pas +0 -0
  111. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_attributes.pas +0 -0
  112. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_basic.pas +0 -0
  113. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_consumer.pas +0 -0
  114. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_generics.pas +0 -0
  115. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_inheritance.pas +0 -0
  116. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_math.pas +0 -0
  117. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_properties.pas +0 -0
  118. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_sections.pas +0 -0
  119. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_statements.pas +0 -0
  120. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_types.pas +0 -0
  121. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_unresolved.pas +0 -0
  122. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/fixtures/unit_with.pas +0 -0
  123. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_agent_metrics.py +0 -0
  124. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_agent_protocol.py +0 -0
  125. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_agent_relations.py +0 -0
  126. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_bootstrap_vllm_codebase_skill.py +0 -0
  127. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_bootstrap_vllm_opencode.py +0 -0
  128. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_delphiast_lexer.py +0 -0
  129. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_delphiast_native_compatibility.py +0 -0
  130. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_delphiast_parser.py +0 -0
  131. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_delphiast_semantic_compatibility.py +0 -0
  132. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_diagnostics.py +0 -0
  133. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_github_performance_corpus.py +0 -0
  134. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_language_feature_matrix.py +0 -0
  135. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_legacy_snippets.py +0 -0
  136. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_lsp_features.py +0 -0
  137. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_lsp_support.py +0 -0
  138. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_metrics.py +0 -0
  139. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_navigation_cache.py +0 -0
  140. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_opencode_config.py +0 -0
  141. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_opencode_probe.py +0 -0
  142. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_opencode_request_payloads.py +0 -0
  143. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_openrouter_github_e2e.py +0 -0
  144. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_ornith_cache_prepare.py +0 -0
  145. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_parallel_outline.py +0 -0
  146. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_parser.py +0 -0
  147. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_parser_backends.py +0 -0
  148. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_preprocessor.py +0 -0
  149. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_progress.py +0 -0
  150. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_project_indexer.py +0 -0
  151. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_release_evidence.py +0 -0
  152. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_semantic.py +0 -0
  153. {python_delphi_lsp-2.3.1 → python_delphi_lsp-2.3.2}/tests/test_vllm_start_script.py +0 -0
  154. {python_delphi_lsp-2.3.1 → 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.1
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.1 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.1 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"
@@ -639,6 +639,21 @@ def _read_startup_tail(diagnostics: object, *, max_bytes: int = _STARTUP_DIAGNOS
639
639
  return b""
640
640
 
641
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
+
642
657
  class _CacheService:
643
658
  def __init__(self, metadata: CacheMetadata) -> None:
644
659
  self.metadata = metadata
@@ -921,11 +936,7 @@ def _start_cache_unlocked(
921
936
  ]
922
937
  if project:
923
938
  command.extend(("--project-file", project))
924
- options: dict[str, object] = {"stdin": subprocess.DEVNULL, "stdout": subprocess.DEVNULL}
925
- if os.name == "nt":
926
- options["creationflags"] = subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
927
- else:
928
- options["start_new_session"] = True
939
+ options = _daemon_process_options()
929
940
  with tempfile.TemporaryFile() as diagnostics:
930
941
  options["stderr"] = diagnostics
931
942
  process = subprocess.Popen(command, **options)
@@ -151,7 +151,14 @@ def build_parser() -> argparse.ArgumentParser:
151
151
 
152
152
  def _add_cache_start_arguments(parser: argparse.ArgumentParser) -> None:
153
153
  parser.add_argument("--root", type=Path, default=Path("."))
154
- parser.add_argument("--project-file", type=Path)
154
+ parser.add_argument(
155
+ "--project-file",
156
+ type=Path,
157
+ help=(
158
+ "Optionally select a .dproj, .dpr, or .dpk file, relative to "
159
+ "--root if needed."
160
+ ),
161
+ )
155
162
  parser.add_argument("--max-memory", type=parse_memory_size, default=DEFAULT_MAX_MEMORY_BYTES)
156
163
  parser.add_argument("--workers", type=parse_worker_setting, default=0)
157
164
  parser.add_argument("--idle-timeout", type=_positive_integer, default=DEFAULT_IDLE_TIMEOUT)
@@ -853,6 +853,7 @@ class AgentContext:
853
853
  )
854
854
  elif request.project_id:
855
855
  self._focus = Focus(project_id=self._workspace.active_project_id)
856
+ self._require_registry(revision)
856
857
  return self._response(request, revision, [self._focus.to_mapping()])
857
858
 
858
859
  def _handle_metrics(self, request: AgentRequest, revision: str) -> AgentResponse:
@@ -992,7 +993,8 @@ class AgentContext:
992
993
  if not project_id:
993
994
  raise AgentProtocolError(
994
995
  "project_required",
995
- "Select a project before querying symbols.",
996
+ "Multiple projects were found. Run 'query open', then select one "
997
+ "with 'query focus --project-id PROJECT_ID'.",
996
998
  )
997
999
  return project_id
998
1000
 
@@ -220,7 +220,7 @@ Inspect Delphi/Object Pascal only through `delphi_codebase`; never raw bash/read
220
220
 
221
221
  ## Protocol v2 workflow
222
222
 
223
- 1. Call `open`. If the requested project is not active, select it with `focus(project_id)`.
223
+ 1. Call `open`. A multi-project repository is active as one workspace by default; select a concrete project with `focus(project_id)` only when project-specific compiler context is needed.
224
224
  2. Call `find` with a narrow query, then `focus(target_id)` for the returned target.
225
225
  3. Inspect focused details in this order as needed: `summary`, `declaration`, `members`, `context`, `body`, `implementations`.
226
226
  4. Trace relations with `references`, `callers`, `callees`, `uses`, `used_by`, `inherits`, or `implements`.
@@ -112,6 +112,21 @@ class AgentWorkspace:
112
112
  )
113
113
  projects: list[AgentProject] = []
114
114
  project_paths: dict[str, Path | None] = {}
115
+ default_project_id = ""
116
+ if (
117
+ resolved_project_file is None
118
+ and len(discovery.project_files) > 1
119
+ ):
120
+ default_project_id = make_target_id("project", "", "workspace")
121
+ projects.append(
122
+ AgentProject(
123
+ project_id=default_project_id,
124
+ name="Workspace",
125
+ path=".",
126
+ kind="workspace",
127
+ )
128
+ )
129
+ project_paths[default_project_id] = None
115
130
  if discovery.project_files:
116
131
  for value in discovery.project_files:
117
132
  path = Path(value)
@@ -140,7 +155,9 @@ class AgentWorkspace:
140
155
  project_paths[project_id] = None
141
156
 
142
157
  workspace = cls(root_path, discovery, tuple(projects), project_paths)
143
- if len(projects) == 1:
158
+ if default_project_id:
159
+ workspace.select_project(default_project_id)
160
+ elif len(projects) == 1:
144
161
  workspace.select_project(projects[0].project_id)
145
162
  return workspace
146
163
 
@@ -305,7 +322,11 @@ class AgentWorkspace:
305
322
  project_path = self._project_paths[project_id]
306
323
  cached = self._project_cache.get(project_id)
307
324
  if project_path is None:
308
- discovery = self._discovery if cached is None else discover_workspace_sources(self._root)
325
+ discovery = (
326
+ self._discovery
327
+ if cached is None and self._discovery.source_files
328
+ else discover_workspace_sources(self._root)
329
+ )
309
330
  else:
310
331
  discovery = discover_delphi_project(
311
332
  self._root,
@@ -146,7 +146,21 @@ def discover_delphi_project(
146
146
  for value in defines:
147
147
  add_define(value)
148
148
 
149
- candidates = _project_candidates(root_path, project_path)
149
+ explicit_dproj: Path | None = None
150
+ if (
151
+ project_path is not None
152
+ and project_path.suffix.casefold() == ".dproj"
153
+ ):
154
+ explicit_dproj = project_path
155
+ project_path = _project_entry_from_dproj(
156
+ explicit_dproj,
157
+ discovery,
158
+ )
159
+ candidates = (
160
+ []
161
+ if explicit_dproj is not None and project_path is None
162
+ else _project_candidates(root_path, project_path)
163
+ )
150
164
  for project in candidates:
151
165
  key = str(project).casefold()
152
166
  if key not in seen_projects:
@@ -160,8 +174,13 @@ def discover_delphi_project(
160
174
  seen_search,
161
175
  discovery.search_path_origins,
162
176
  )
163
- dproj = project.with_suffix(".dproj")
164
- if dproj.exists():
177
+ dproj_candidates = [project.with_suffix(".dproj")]
178
+ if explicit_dproj is not None:
179
+ dproj_candidates.insert(0, explicit_dproj)
180
+ for dproj in dproj_candidates:
181
+ config_key = str(dproj).casefold()
182
+ if config_key in seen_configs or not dproj.exists():
183
+ continue
165
184
  _read_dproj(
166
185
  dproj,
167
186
  discovery,
@@ -173,7 +192,7 @@ def discover_delphi_project(
173
192
  discovery.include_path_origins,
174
193
  add_define,
175
194
  )
176
- seen_configs.add(str(dproj).casefold())
195
+ seen_configs.add(config_key)
177
196
  discovery.config_files.append(str(dproj))
178
197
  for cfg in (project.with_suffix(".cfg"), project.with_suffix(".dof")):
179
198
  if cfg.exists():
@@ -483,6 +502,72 @@ def _main_source_from_dproj(path: Path) -> str | None:
483
502
  return None
484
503
 
485
504
 
505
+ def _project_entry_from_dproj(
506
+ path: Path,
507
+ discovery: DelphiProjectDiscovery,
508
+ ) -> Path | None:
509
+ try:
510
+ root = ET.parse(path).getroot()
511
+ except ET.ParseError as exc:
512
+ discovery.problems.append(
513
+ DiscoveryProblem(
514
+ "cant_read_project",
515
+ f"Could not parse Delphi project file: {exc}",
516
+ str(path),
517
+ )
518
+ )
519
+ return None
520
+ except OSError as exc:
521
+ discovery.problems.append(
522
+ DiscoveryProblem(
523
+ "cant_read_project",
524
+ f"Could not read Delphi project file: {exc}",
525
+ str(path),
526
+ )
527
+ )
528
+ return None
529
+
530
+ main_source = next(
531
+ (
532
+ (element.text or "").strip()
533
+ for element in root.iter()
534
+ if _xml_local_name(element.tag) == "MainSource"
535
+ and (element.text or "").strip()
536
+ ),
537
+ "",
538
+ )
539
+ if not main_source:
540
+ discovery.problems.append(
541
+ DiscoveryProblem(
542
+ "cant_read_project",
543
+ "Delphi project file does not define MainSource.",
544
+ str(path),
545
+ )
546
+ )
547
+ return None
548
+
549
+ entry = (path.parent / main_source).resolve()
550
+ if entry.suffix.casefold() not in PROJECT_EXTENSIONS:
551
+ discovery.problems.append(
552
+ DiscoveryProblem(
553
+ "cant_read_project",
554
+ "MainSource must reference a .dpr or .dpk entry file.",
555
+ str(path),
556
+ )
557
+ )
558
+ return None
559
+ if not entry.is_file():
560
+ discovery.problems.append(
561
+ DiscoveryProblem(
562
+ "cant_read_project",
563
+ f"MainSource does not exist: {entry}",
564
+ str(path),
565
+ )
566
+ )
567
+ return None
568
+ return entry
569
+
570
+
486
571
  def _scan_sources(
487
572
  root: Path,
488
573
  discovery: DelphiProjectDiscovery,
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-delphi-lsp"
7
- version = "2.3.1"
7
+ version = "2.3.2"
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: 2.3.1
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.1 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
 
@@ -332,6 +332,45 @@ def test_startup_timeout_replaces_the_old_ten_second_deadline(
332
332
  assert process.killed is True
333
333
 
334
334
 
335
+ def test_windows_cache_daemon_starts_without_a_console_window(
336
+ monkeypatch: pytest.MonkeyPatch,
337
+ ) -> None:
338
+ from delphi_lsp import agent_cache
339
+
340
+ create_no_window = 0x08000000
341
+ create_new_process_group = 0x00000200
342
+ detached_process = 0x00000008
343
+ monkeypatch.setattr(agent_cache.os, "name", "nt")
344
+ monkeypatch.setattr(
345
+ agent_cache.subprocess,
346
+ "CREATE_NO_WINDOW",
347
+ create_no_window,
348
+ raising=False,
349
+ )
350
+ monkeypatch.setattr(
351
+ agent_cache.subprocess,
352
+ "CREATE_NEW_PROCESS_GROUP",
353
+ create_new_process_group,
354
+ raising=False,
355
+ )
356
+ monkeypatch.setattr(
357
+ agent_cache.subprocess,
358
+ "DETACHED_PROCESS",
359
+ detached_process,
360
+ raising=False,
361
+ )
362
+
363
+ options = agent_cache._daemon_process_options()
364
+
365
+ assert options["creationflags"] == (
366
+ create_no_window | create_new_process_group
367
+ )
368
+ assert options["creationflags"] & detached_process == 0
369
+ assert options["stdin"] is agent_cache.subprocess.DEVNULL
370
+ assert options["stdout"] is agent_cache.subprocess.DEVNULL
371
+ assert "start_new_session" not in options
372
+
373
+
335
374
  @pytest.mark.parametrize("startup_timeout", [0, -1, float("nan"), float("inf")])
336
375
  def test_start_cache_rejects_non_positive_startup_timeout(
337
376
  tmp_path: Path,
@@ -453,6 +492,72 @@ def test_prewarm_only_tolerates_project_selection_error(monkeypatch, tmp_path: P
453
492
  service.prewarm()
454
493
 
455
494
 
495
+ def test_multiple_projects_prewarm_repository_navigation_cache(
496
+ tmp_path: Path,
497
+ ) -> None:
498
+ from delphi_lsp.agent_cache import CacheMetadata, _CacheService
499
+
500
+ write_source(
501
+ tmp_path / "A.dpr",
502
+ "program A; uses AUnit in 'AUnit.pas'; begin end.",
503
+ )
504
+ write_source(
505
+ tmp_path / "B.dpr",
506
+ "program B; uses BUnit in 'BUnit.pas'; begin end.",
507
+ )
508
+ write_source(
509
+ tmp_path / "AUnit.pas",
510
+ """
511
+ unit AUnit;
512
+ interface
513
+ type
514
+ TARepositoryCache = class
515
+ end;
516
+ implementation
517
+ end.
518
+ """,
519
+ )
520
+ write_source(
521
+ tmp_path / "BUnit.pas",
522
+ """
523
+ unit BUnit;
524
+ interface
525
+ type
526
+ TBRepositoryCache = class
527
+ end;
528
+ implementation
529
+ end.
530
+ """,
531
+ )
532
+ metadata = CacheMetadata(
533
+ 2,
534
+ str(tmp_path.resolve()),
535
+ os.getpid(),
536
+ 1,
537
+ "x" * 32,
538
+ "test",
539
+ "",
540
+ 512 * 1024**2,
541
+ 0,
542
+ 10,
543
+ time.time(),
544
+ )
545
+ service = _CacheService(metadata)
546
+
547
+ service.prewarm()
548
+
549
+ assert service.context.navigation_cache_is_warm
550
+ assert service.cache_state == "warm"
551
+ assert service.status()["current_bytes"] > 0
552
+ result = service.request(
553
+ {"action": "find", "query": "RepositoryCache"}
554
+ ).payload["result"]
555
+ assert {item["name"] for item in result} == {
556
+ "TARepositoryCache",
557
+ "TBRepositoryCache",
558
+ }
559
+
560
+
456
561
  def test_cache_service_uses_constant_time_accounting_instead_of_deep_graph_walk(
457
562
  monkeypatch: pytest.MonkeyPatch,
458
563
  tmp_path: Path,
@@ -735,6 +840,7 @@ def test_readme_documents_bounded_cache_daemon_commands_and_retention_contract()
735
840
  "delphi-lsp-agent cache stop --root PATH",
736
841
  "delphi-lsp-agent query --root PATH find TCustomer",
737
842
  "delphi-lsp-agent query --root PATH focus TARGET_ID",
843
+ "delphi-lsp-agent query --root PATH focus --project-id PROJECT_ID",
738
844
  "delphi-lsp-agent query --root PATH inspect",
739
845
  "delphi-lsp-agent query --root PATH trace TARGET_ID --relation callers",
740
846
  "delphi-lsp-agent query --root PATH metrics UNIT_QUERY",
@@ -755,6 +861,8 @@ def test_readme_documents_bounded_cache_daemon_commands_and_retention_contract()
755
861
  assert "status JSON" in readme
756
862
  assert "Protocol v2 JSON" in readme
757
863
  assert "writes warnings to stderr" in readme
864
+ assert "A `.dproj` is optional" in readme
865
+ assert "Selecting a project this way also prewarms" in readme
758
866
  assert "Eviction is ordered" in readme
759
867
  assert "auxiliary caches are evicted first" in readme
760
868
  assert "rebuilds the navigation state on demand" in readme
@@ -64,6 +64,18 @@ def test_query_cli_rejects_protocol_limits_before_contacting_cache(
64
64
  parser.parse_args(["query", "open", option, value])
65
65
 
66
66
 
67
+ def test_cache_start_help_documents_optional_dproj(
68
+ capsys: pytest.CaptureFixture[str],
69
+ ) -> None:
70
+ parser = agent_cli.build_parser()
71
+
72
+ with pytest.raises(SystemExit) as stopped:
73
+ parser.parse_args(["cache", "start", "--help"])
74
+
75
+ assert stopped.value.code == 0
76
+ assert ".dproj" in capsys.readouterr().out
77
+
78
+
67
79
  @pytest.mark.parametrize("value", [0, -1, True])
68
80
  def test_start_cache_rejects_non_positive_idle_timeout(
69
81
  tmp_path: Path,