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.
- {python_delphi_lsp-2.1.0/python_delphi_lsp.egg-info → python_delphi_lsp-2.3.0}/PKG-INFO +30 -12
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/README.md +28 -11
- python_delphi_lsp-2.3.0/delphi_lsp/_version.py +1 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/agent_cache.py +64 -6
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/agent_context.py +721 -161
- python_delphi_lsp-2.3.0/delphi_lsp/agent_metrics.py +179 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/agent_protocol.py +4 -3
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/agent_relations.py +300 -29
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/agent_workspace.py +51 -3
- python_delphi_lsp-2.3.0/delphi_lsp/delphiast_lexer.py +284 -0
- python_delphi_lsp-2.3.0/delphi_lsp/delphiast_parser.py +1498 -0
- python_delphi_lsp-2.3.0/delphi_lsp/delphiast_tokens.py +41 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/metrics.py +80 -8
- python_delphi_lsp-2.3.0/delphi_lsp/navigation_cache.py +134 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/parallel_outline.py +49 -10
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/parser.py +54 -4
- python_delphi_lsp-2.3.0/delphi_lsp/parser_backend.py +57 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/preprocessor.py +56 -20
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/project_indexer.py +32 -1
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/pyproject.toml +2 -1
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0/python_delphi_lsp.egg-info}/PKG-INFO +30 -12
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/python_delphi_lsp.egg-info/SOURCES.txt +11 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/python_delphi_lsp.egg-info/requires.txt +1 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_agent_cache.py +109 -6
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_agent_context.py +391 -12
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_agent_metrics.py +36 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_agent_protocol.py +31 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_agent_relations.py +55 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_agent_workspace.py +35 -0
- python_delphi_lsp-2.3.0/tests/test_delphiast_lexer.py +97 -0
- python_delphi_lsp-2.3.0/tests/test_delphiast_native_compatibility.py +364 -0
- python_delphi_lsp-2.3.0/tests/test_delphiast_parser.py +121 -0
- python_delphi_lsp-2.3.0/tests/test_delphiast_semantic_compatibility.py +74 -0
- python_delphi_lsp-2.3.0/tests/test_navigation_cache.py +43 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_package_metadata.py +3 -3
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_parallel_outline.py +31 -3
- python_delphi_lsp-2.3.0/tests/test_parser_backends.py +135 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_preprocessor.py +57 -1
- python_delphi_lsp-2.1.0/delphi_lsp/_version.py +0 -1
- python_delphi_lsp-2.1.0/delphi_lsp/agent_metrics.py +0 -82
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/.github/workflows/ci.yml +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/LICENSE +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/MANIFEST.in +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/__init__.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/agent_cli.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/agent_layers.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/agent_templates.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/binary.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/comment_builder.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/consts.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/grammar.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/lark_builder.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/lark_tokens.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/lsp_server.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/nodes.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/progress.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/project_discovery.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/semantic.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/semantic_builder.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/source_reader.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/workspace.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/delphi_lsp/writer.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/opencode.json +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/python_delphi_lsp.egg-info/dependency_links.txt +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/python_delphi_lsp.egg-info/entry_points.txt +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/python_delphi_lsp.egg-info/top_level.txt +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/audit_delphi_language_features.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/benchmark_github_corpus.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/benchmark_parallel_cache.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/bootstrap_vllm_codebase_skill_test.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/bootstrap_vllm_opencode_test.ps1 +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/bootstrap_vllm_opencode_test.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/bootstrap_vllm_opencode_test.sh +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/build_github_performance_corpus.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/check_ornith_cache.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/generate_progress_pdf.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/generate_release_evidence.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/ollama/ornith-lspctx.Modelfile +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/prepare_ornith_cache.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/run_opencode_lsp_probe.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/run_openrouter_github_e2e.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/start_ornith_vllm.sh +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/scripts/summarize_opencode_request_payloads.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/setup.cfg +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/corpora.performance.lock.json +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/DeprecatedOnConst.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/VariantRecordFieldAttributes.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/alignedrecords.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/constset.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/deprecatedtype.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/dottedtypes.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/endtoken.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/experimentals.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/externalfunction.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/finalizationinitializationexports.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/forwardoverloaded.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/forwardwithoutsemicolon.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/genericconstraints.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/genericinterfacemethoddelegation.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/implementsgenerictype.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/include file2.inc +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/includefile.inc +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/includefile.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/managedrecords.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/messagemethod.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/multiline.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/nonalignedrecords.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/numbers.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/pointerchars.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/properties.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/strictvisibility.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/tryexcept.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/umlauts.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/legacy_snippets/whitespacearoundifdefcondition.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/library_demo.dpr +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/package_demo.dpk +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/program_demo.dpr +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_advanced.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_attributes.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_basic.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_consumer.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_generics.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_inheritance.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_math.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_properties.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_sections.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_statements.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_types.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_unresolved.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/fixtures/unit_with.pas +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_agent_codebase.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_agent_worker.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_bootstrap_vllm_codebase_skill.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_bootstrap_vllm_opencode.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_diagnostics.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_github_performance_corpus.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_language_feature_matrix.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_legacy_snippets.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_lsp_features.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_lsp_support.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_metrics.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_opencode_config.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_opencode_probe.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_opencode_request_payloads.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_openrouter_github_e2e.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_ornith_cache_prepare.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_parser.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_progress.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_project_discovery.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_project_indexer.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_release_evidence.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_semantic.py +0 -0
- {python_delphi_lsp-2.1.0 → python_delphi_lsp-2.3.0}/tests/test_vllm_start_script.py +0 -0
- {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.
|
|
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.
|
|
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
|
|
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 `
|
|
244
|
-
task, CPU, and
|
|
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
|
|
249
|
-
|
|
250
|
-
the
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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`,
|
|
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.
|
|
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
|
|
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 `
|
|
205
|
-
task, CPU, and
|
|
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
|
|
210
|
-
|
|
211
|
-
the
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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`,
|
|
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(
|
|
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.
|
|
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:
|
|
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:
|
|
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.
|
|
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
|
|