python-delphi-lsp 2.0.0__tar.gz → 2.0.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.
- {python_delphi_lsp-2.0.0/python_delphi_lsp.egg-info → python_delphi_lsp-2.0.2}/PKG-INFO +88 -33
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/README.md +87 -32
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/__init__.py +20 -0
- python_delphi_lsp-2.0.2/delphi_lsp/_version.py +1 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/agent_cli.py +13 -5
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/agent_context.py +64 -22
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/agent_layers.py +65 -0
- python_delphi_lsp-2.0.2/delphi_lsp/agent_metrics.py +82 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/agent_protocol.py +1 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/agent_templates.py +94 -68
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/agent_workspace.py +35 -1
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/lark_builder.py +14 -2
- python_delphi_lsp-2.0.2/delphi_lsp/metrics.py +759 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/pyproject.toml +1 -1
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2/python_delphi_lsp.egg-info}/PKG-INFO +88 -33
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/python_delphi_lsp.egg-info/SOURCES.txt +4 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/scripts/bootstrap_vllm_codebase_skill_test.py +186 -8
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_agent_codebase.py +200 -61
- python_delphi_lsp-2.0.2/tests/test_agent_metrics.py +149 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_agent_protocol.py +10 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_agent_worker.py +2 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_bootstrap_vllm_codebase_skill.py +63 -15
- python_delphi_lsp-2.0.2/tests/test_metrics.py +188 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_opencode_probe.py +1 -1
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_package_metadata.py +18 -4
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_parser.py +50 -0
- python_delphi_lsp-2.0.0/delphi_lsp/_version.py +0 -1
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/LICENSE +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/MANIFEST.in +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/agent_relations.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/binary.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/comment_builder.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/consts.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/grammar.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/lark_tokens.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/lsp_server.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/nodes.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/parser.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/preprocessor.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/project_discovery.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/project_indexer.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/semantic.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/semantic_builder.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/source_reader.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/workspace.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/delphi_lsp/writer.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/opencode.json +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/python_delphi_lsp.egg-info/dependency_links.txt +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/python_delphi_lsp.egg-info/entry_points.txt +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/python_delphi_lsp.egg-info/requires.txt +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/python_delphi_lsp.egg-info/top_level.txt +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/scripts/audit_delphi_language_features.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/scripts/bootstrap_vllm_opencode_test.ps1 +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/scripts/bootstrap_vllm_opencode_test.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/scripts/bootstrap_vllm_opencode_test.sh +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/scripts/check_ornith_cache.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/scripts/generate_release_evidence.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/scripts/ollama/ornith-lspctx.Modelfile +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/scripts/prepare_ornith_cache.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/scripts/run_opencode_lsp_probe.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/scripts/start_ornith_vllm.sh +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/scripts/summarize_opencode_request_payloads.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/setup.cfg +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/DeprecatedOnConst.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/VariantRecordFieldAttributes.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/alignedrecords.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/constset.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/deprecatedtype.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/dottedtypes.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/endtoken.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/experimentals.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/externalfunction.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/finalizationinitializationexports.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/forwardoverloaded.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/forwardwithoutsemicolon.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/genericconstraints.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/genericinterfacemethoddelegation.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/implementsgenerictype.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/include file2.inc +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/includefile.inc +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/includefile.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/managedrecords.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/messagemethod.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/multiline.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/nonalignedrecords.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/numbers.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/pointerchars.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/properties.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/strictvisibility.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/tryexcept.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/umlauts.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/legacy_snippets/whitespacearoundifdefcondition.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/library_demo.dpr +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/package_demo.dpk +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/program_demo.dpr +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/unit_advanced.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/unit_attributes.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/unit_basic.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/unit_consumer.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/unit_generics.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/unit_inheritance.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/unit_math.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/unit_properties.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/unit_sections.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/unit_statements.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/unit_types.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/unit_unresolved.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/fixtures/unit_with.pas +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_agent_context.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_agent_relations.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_agent_workspace.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_bootstrap_vllm_opencode.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_diagnostics.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_language_feature_matrix.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_legacy_snippets.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_lsp_features.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_lsp_support.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_opencode_config.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_opencode_request_payloads.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_ornith_cache_prepare.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_preprocessor.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_project_discovery.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_project_indexer.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_release_evidence.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_semantic.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.2}/tests/test_vllm_start_script.py +0 -0
- {python_delphi_lsp-2.0.0 → python_delphi_lsp-2.0.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.0.
|
|
3
|
+
Version: 2.0.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
|
|
@@ -41,7 +41,7 @@ Dynamic: license-file
|
|
|
41
41
|
|
|
42
42
|
`python-delphi-lsp` parses Delphi/Object Pascal, builds semantic and project
|
|
43
43
|
indexes, serves LSP, and provides bounded codebase navigation for agents.
|
|
44
|
-
Version 2.0.
|
|
44
|
+
Version 2.0.2 is authored by Dark Light and supports Windows, macOS, and Linux.
|
|
45
45
|
|
|
46
46
|
## Install and quick start
|
|
47
47
|
|
|
@@ -82,6 +82,39 @@ project = ProjectIndexer(
|
|
|
82
82
|
print(project.parsed_units)
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
## Architecture metrics
|
|
86
|
+
|
|
87
|
+
The public metrics API analyzes a single unit or aggregates a complete project:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from delphi_lsp import analyze_project, analyze_unit
|
|
91
|
+
|
|
92
|
+
unit = analyze_unit(
|
|
93
|
+
"unit Alpha; interface implementation procedure Run; begin end; end.",
|
|
94
|
+
"Alpha.pas",
|
|
95
|
+
)
|
|
96
|
+
print(unit.lines.total_lines, unit.cyclomatic.maximum)
|
|
97
|
+
|
|
98
|
+
project = analyze_project({
|
|
99
|
+
"Main.dpr": "program Main; uses Alpha; begin end.",
|
|
100
|
+
"Alpha.pas": "unit Alpha; interface implementation end.",
|
|
101
|
+
})
|
|
102
|
+
print(project.total_loc)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Line results distinguish total, source, blank, comment-only, mixed-comment, and
|
|
106
|
+
compiler-directive lines. Project `total_loc` counts each `.dpr`, `.dpk`, and
|
|
107
|
+
`.pas` source once; `include_loc` counts unique `.inc` inputs separately, and
|
|
108
|
+
`total_loc_with_includes` combines both totals.
|
|
109
|
+
|
|
110
|
+
Cyclomatic complexity is reported per routine and as unit/project aggregates.
|
|
111
|
+
The result also includes complete Halstead counts and derived values, a
|
|
112
|
+
normalized 0–100 maintainability index, symbol counts, dependency edges,
|
|
113
|
+
afferent coupling (fan-in), efferent coupling (fan-out), instability,
|
|
114
|
+
abstractness, and distance from the main sequence. Coupling detail separates
|
|
115
|
+
internal from external dependencies. Empty or partial inputs produce finite
|
|
116
|
+
JSON values; unreadable agent-workspace inputs are reported as metric problems.
|
|
117
|
+
|
|
85
118
|
Run the stdio language server with `delphi-lsp`, or equivalently:
|
|
86
119
|
|
|
87
120
|
```bash
|
|
@@ -144,19 +177,27 @@ delphi-lsp-agent view --root PATH [--project-file FILE] --layer LAYER
|
|
|
144
177
|
delphi-lsp-agent index --root PATH [--project-file FILE] [--out FILE]
|
|
145
178
|
delphi-lsp-agent skill install [--target PATH] [--force]
|
|
146
179
|
delphi-lsp-agent opencode install [--target PATH] [--python PYTHON]
|
|
147
|
-
[--force] [--write-config]
|
|
180
|
+
[--force] [--write-agent|--write-config]
|
|
148
181
|
delphi-lsp-agent worker --root PATH [--project-file FILE]
|
|
149
182
|
```
|
|
150
183
|
|
|
151
184
|
`view --layer` accepts `overview`, `projects`, `units`, `unit`,
|
|
152
|
-
`symbols`, `symbol`, `implementation`, `references`,
|
|
185
|
+
`symbols`, `symbol`, `implementation`, `references`, `problems`, and
|
|
186
|
+
`metrics`. For example, `delphi-lsp-agent view --layer metrics --format json`
|
|
187
|
+
returns a project summary and detailed unit metric objects; `--query` filters
|
|
188
|
+
units by name or path.
|
|
153
189
|
`index` materializes overview, projects, and problems JSON. `skill install`
|
|
154
|
-
writes the skill; `opencode install` writes
|
|
155
|
-
|
|
190
|
+
writes the skill; `opencode install` writes the package-named skill, Markdown
|
|
191
|
+
agent, and plugin. The two deprecated write flags are harmless aliases and do
|
|
192
|
+
not change user configuration.
|
|
156
193
|
`worker` serves NDJSON over standard input/output.
|
|
157
194
|
|
|
158
195
|
Protocol v2 actions are `open`, `find`, `inspect`, `trace`, `focus`,
|
|
159
|
-
and `
|
|
196
|
+
`problems`, and `metrics`. A `metrics` request without a query returns the
|
|
197
|
+
project summary followed by unit cards. A query filters units, while a unit
|
|
198
|
+
`target_id` from `open` selects one unit; `detail: "members"` adds routine,
|
|
199
|
+
Halstead, dependency, and symbol-count detail without returning source text.
|
|
200
|
+
Detail values are `summary`, `declaration`, `members`,
|
|
160
201
|
`context`, `body`, and `implementations`. Relations are `references`,
|
|
161
202
|
`callers`, `callees`, `uses`, `used_by`, `inherits`, and
|
|
162
203
|
`implements`.
|
|
@@ -185,52 +226,54 @@ optimization does not remove LSP functionality.
|
|
|
185
226
|
Install the generated integration in a worktree:
|
|
186
227
|
|
|
187
228
|
```bash
|
|
188
|
-
delphi-lsp-agent opencode install --target .
|
|
229
|
+
delphi-lsp-agent opencode install --target .
|
|
189
230
|
```
|
|
190
231
|
|
|
191
232
|
It writes:
|
|
192
233
|
|
|
193
234
|
```text
|
|
194
|
-
.agents/skills/delphi-
|
|
235
|
+
.agents/skills/python-delphi-lsp/SKILL.md
|
|
195
236
|
.opencode/plugins/delphi_codebase.ts
|
|
237
|
+
.opencode/agents/python-delphi-lsp.md
|
|
196
238
|
```
|
|
197
239
|
|
|
198
|
-
The
|
|
199
|
-
`delphi-
|
|
240
|
+
The package-named Markdown agent enables only the
|
|
241
|
+
`python-delphi-lsp` skill and `delphi_codebase`. It denies
|
|
200
242
|
`bash`, `read`, `glob`, `grep`, and `lsp`, along with edit/write and
|
|
201
243
|
other raw source tools. The skill is enabled. The installer does not use the
|
|
202
|
-
retired `.opencode/tools` path.
|
|
244
|
+
retired `.opencode/tools` path and never reads or changes `opencode.json`; that
|
|
245
|
+
file remains entirely user-owned. The deprecated `--write-config` and
|
|
246
|
+
`--write-agent` options are accepted harmlessly for compatibility.
|
|
203
247
|
|
|
204
248
|
The plugin maintains one worker per session/root, reusing focus and indexes.
|
|
205
249
|
During compaction it restores the focus and summary into the new context.
|
|
206
250
|
Transport failure, session deletion, and plugin disposal clean up the worker.
|
|
207
251
|
|
|
208
|
-
A generated OpenCode agent
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
"lsp": "deny"
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
252
|
+
A generated OpenCode agent starts with this Markdown frontmatter:
|
|
253
|
+
|
|
254
|
+
```markdown
|
|
255
|
+
---
|
|
256
|
+
description: Inspect Delphi and Object Pascal codebases through python-delphi-lsp.
|
|
257
|
+
mode: subagent
|
|
258
|
+
temperature: 0
|
|
259
|
+
permission:
|
|
260
|
+
"*": deny
|
|
261
|
+
delphi_codebase: allow
|
|
262
|
+
skill:
|
|
263
|
+
"*": deny
|
|
264
|
+
python-delphi-lsp: allow
|
|
265
|
+
---
|
|
227
266
|
```
|
|
228
267
|
|
|
229
|
-
Select `
|
|
230
|
-
|
|
268
|
+
Select `python-delphi-lsp`, ask it to load the `python-delphi-lsp` skill, then
|
|
269
|
+
use `delphi_codebase` actions such as
|
|
231
270
|
`open`, `find`, `focus`, and `inspect`. Use semantic tool calls, not raw
|
|
232
271
|
source tools.
|
|
233
272
|
|
|
273
|
+
For architecture questions, call `metrics` without a query to compare unit
|
|
274
|
+
cards and read project LOC. Then select a returned unit ID with another
|
|
275
|
+
`metrics` call and `detail: "members"` to inspect its routines and coupling.
|
|
276
|
+
|
|
234
277
|
For the root LSP configuration, a local model can be used as follows:
|
|
235
278
|
|
|
236
279
|
```bash
|
|
@@ -297,6 +340,18 @@ local model server, increase it explicitly with `--probe-timeout SECONDS`.
|
|
|
297
340
|
|
|
298
341
|
The bundled automatic helper is not a cross-platform startup mechanism.
|
|
299
342
|
|
|
343
|
+
The architecture-metrics proof uses a separate deterministic 34-LOC project.
|
|
344
|
+
It requires the restricted model to load the skill, call `metrics` for the
|
|
345
|
+
project and most-complex unit, and report exact LOC, cyclomatic maximum, and
|
|
346
|
+
instability values. Raw source, search, shell, and write tools remain forbidden:
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
python scripts/bootstrap_vllm_codebase_skill_test.py --probe metrics --use-running-server
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Use `--probe metrics --start-vllm --max-model-len 24576` for the cached macOS
|
|
353
|
+
auto-start path when no endpoint is already running.
|
|
354
|
+
|
|
300
355
|
## Migration to 2.0
|
|
301
356
|
|
|
302
357
|
`delphi_lsp` is the only supported import namespace. Update imports directly;
|
|
@@ -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.0.
|
|
5
|
+
Version 2.0.2 is authored by Dark Light and supports Windows, macOS, and Linux.
|
|
6
6
|
|
|
7
7
|
## Install and quick start
|
|
8
8
|
|
|
@@ -43,6 +43,39 @@ project = ProjectIndexer(
|
|
|
43
43
|
print(project.parsed_units)
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
## Architecture metrics
|
|
47
|
+
|
|
48
|
+
The public metrics API analyzes a single unit or aggregates a complete project:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from delphi_lsp import analyze_project, analyze_unit
|
|
52
|
+
|
|
53
|
+
unit = analyze_unit(
|
|
54
|
+
"unit Alpha; interface implementation procedure Run; begin end; end.",
|
|
55
|
+
"Alpha.pas",
|
|
56
|
+
)
|
|
57
|
+
print(unit.lines.total_lines, unit.cyclomatic.maximum)
|
|
58
|
+
|
|
59
|
+
project = analyze_project({
|
|
60
|
+
"Main.dpr": "program Main; uses Alpha; begin end.",
|
|
61
|
+
"Alpha.pas": "unit Alpha; interface implementation end.",
|
|
62
|
+
})
|
|
63
|
+
print(project.total_loc)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Line results distinguish total, source, blank, comment-only, mixed-comment, and
|
|
67
|
+
compiler-directive lines. Project `total_loc` counts each `.dpr`, `.dpk`, and
|
|
68
|
+
`.pas` source once; `include_loc` counts unique `.inc` inputs separately, and
|
|
69
|
+
`total_loc_with_includes` combines both totals.
|
|
70
|
+
|
|
71
|
+
Cyclomatic complexity is reported per routine and as unit/project aggregates.
|
|
72
|
+
The result also includes complete Halstead counts and derived values, a
|
|
73
|
+
normalized 0–100 maintainability index, symbol counts, dependency edges,
|
|
74
|
+
afferent coupling (fan-in), efferent coupling (fan-out), instability,
|
|
75
|
+
abstractness, and distance from the main sequence. Coupling detail separates
|
|
76
|
+
internal from external dependencies. Empty or partial inputs produce finite
|
|
77
|
+
JSON values; unreadable agent-workspace inputs are reported as metric problems.
|
|
78
|
+
|
|
46
79
|
Run the stdio language server with `delphi-lsp`, or equivalently:
|
|
47
80
|
|
|
48
81
|
```bash
|
|
@@ -105,19 +138,27 @@ delphi-lsp-agent view --root PATH [--project-file FILE] --layer LAYER
|
|
|
105
138
|
delphi-lsp-agent index --root PATH [--project-file FILE] [--out FILE]
|
|
106
139
|
delphi-lsp-agent skill install [--target PATH] [--force]
|
|
107
140
|
delphi-lsp-agent opencode install [--target PATH] [--python PYTHON]
|
|
108
|
-
[--force] [--write-config]
|
|
141
|
+
[--force] [--write-agent|--write-config]
|
|
109
142
|
delphi-lsp-agent worker --root PATH [--project-file FILE]
|
|
110
143
|
```
|
|
111
144
|
|
|
112
145
|
`view --layer` accepts `overview`, `projects`, `units`, `unit`,
|
|
113
|
-
`symbols`, `symbol`, `implementation`, `references`,
|
|
146
|
+
`symbols`, `symbol`, `implementation`, `references`, `problems`, and
|
|
147
|
+
`metrics`. For example, `delphi-lsp-agent view --layer metrics --format json`
|
|
148
|
+
returns a project summary and detailed unit metric objects; `--query` filters
|
|
149
|
+
units by name or path.
|
|
114
150
|
`index` materializes overview, projects, and problems JSON. `skill install`
|
|
115
|
-
writes the skill; `opencode install` writes
|
|
116
|
-
|
|
151
|
+
writes the skill; `opencode install` writes the package-named skill, Markdown
|
|
152
|
+
agent, and plugin. The two deprecated write flags are harmless aliases and do
|
|
153
|
+
not change user configuration.
|
|
117
154
|
`worker` serves NDJSON over standard input/output.
|
|
118
155
|
|
|
119
156
|
Protocol v2 actions are `open`, `find`, `inspect`, `trace`, `focus`,
|
|
120
|
-
and `
|
|
157
|
+
`problems`, and `metrics`. A `metrics` request without a query returns the
|
|
158
|
+
project summary followed by unit cards. A query filters units, while a unit
|
|
159
|
+
`target_id` from `open` selects one unit; `detail: "members"` adds routine,
|
|
160
|
+
Halstead, dependency, and symbol-count detail without returning source text.
|
|
161
|
+
Detail values are `summary`, `declaration`, `members`,
|
|
121
162
|
`context`, `body`, and `implementations`. Relations are `references`,
|
|
122
163
|
`callers`, `callees`, `uses`, `used_by`, `inherits`, and
|
|
123
164
|
`implements`.
|
|
@@ -146,52 +187,54 @@ optimization does not remove LSP functionality.
|
|
|
146
187
|
Install the generated integration in a worktree:
|
|
147
188
|
|
|
148
189
|
```bash
|
|
149
|
-
delphi-lsp-agent opencode install --target .
|
|
190
|
+
delphi-lsp-agent opencode install --target .
|
|
150
191
|
```
|
|
151
192
|
|
|
152
193
|
It writes:
|
|
153
194
|
|
|
154
195
|
```text
|
|
155
|
-
.agents/skills/delphi-
|
|
196
|
+
.agents/skills/python-delphi-lsp/SKILL.md
|
|
156
197
|
.opencode/plugins/delphi_codebase.ts
|
|
198
|
+
.opencode/agents/python-delphi-lsp.md
|
|
157
199
|
```
|
|
158
200
|
|
|
159
|
-
The
|
|
160
|
-
`delphi-
|
|
201
|
+
The package-named Markdown agent enables only the
|
|
202
|
+
`python-delphi-lsp` skill and `delphi_codebase`. It denies
|
|
161
203
|
`bash`, `read`, `glob`, `grep`, and `lsp`, along with edit/write and
|
|
162
204
|
other raw source tools. The skill is enabled. The installer does not use the
|
|
163
|
-
retired `.opencode/tools` path.
|
|
205
|
+
retired `.opencode/tools` path and never reads or changes `opencode.json`; that
|
|
206
|
+
file remains entirely user-owned. The deprecated `--write-config` and
|
|
207
|
+
`--write-agent` options are accepted harmlessly for compatibility.
|
|
164
208
|
|
|
165
209
|
The plugin maintains one worker per session/root, reusing focus and indexes.
|
|
166
210
|
During compaction it restores the focus and summary into the new context.
|
|
167
211
|
Transport failure, session deletion, and plugin disposal clean up the worker.
|
|
168
212
|
|
|
169
|
-
A generated OpenCode agent
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
"lsp": "deny"
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
213
|
+
A generated OpenCode agent starts with this Markdown frontmatter:
|
|
214
|
+
|
|
215
|
+
```markdown
|
|
216
|
+
---
|
|
217
|
+
description: Inspect Delphi and Object Pascal codebases through python-delphi-lsp.
|
|
218
|
+
mode: subagent
|
|
219
|
+
temperature: 0
|
|
220
|
+
permission:
|
|
221
|
+
"*": deny
|
|
222
|
+
delphi_codebase: allow
|
|
223
|
+
skill:
|
|
224
|
+
"*": deny
|
|
225
|
+
python-delphi-lsp: allow
|
|
226
|
+
---
|
|
188
227
|
```
|
|
189
228
|
|
|
190
|
-
Select `
|
|
191
|
-
|
|
229
|
+
Select `python-delphi-lsp`, ask it to load the `python-delphi-lsp` skill, then
|
|
230
|
+
use `delphi_codebase` actions such as
|
|
192
231
|
`open`, `find`, `focus`, and `inspect`. Use semantic tool calls, not raw
|
|
193
232
|
source tools.
|
|
194
233
|
|
|
234
|
+
For architecture questions, call `metrics` without a query to compare unit
|
|
235
|
+
cards and read project LOC. Then select a returned unit ID with another
|
|
236
|
+
`metrics` call and `detail: "members"` to inspect its routines and coupling.
|
|
237
|
+
|
|
195
238
|
For the root LSP configuration, a local model can be used as follows:
|
|
196
239
|
|
|
197
240
|
```bash
|
|
@@ -258,6 +301,18 @@ local model server, increase it explicitly with `--probe-timeout SECONDS`.
|
|
|
258
301
|
|
|
259
302
|
The bundled automatic helper is not a cross-platform startup mechanism.
|
|
260
303
|
|
|
304
|
+
The architecture-metrics proof uses a separate deterministic 34-LOC project.
|
|
305
|
+
It requires the restricted model to load the skill, call `metrics` for the
|
|
306
|
+
project and most-complex unit, and report exact LOC, cyclomatic maximum, and
|
|
307
|
+
instability values. Raw source, search, shell, and write tools remain forbidden:
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
python scripts/bootstrap_vllm_codebase_skill_test.py --probe metrics --use-running-server
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Use `--probe metrics --start-vllm --max-model-len 24576` for the cached macOS
|
|
314
|
+
auto-start path when no endpoint is already running.
|
|
315
|
+
|
|
261
316
|
## Migration to 2.0
|
|
262
317
|
|
|
263
318
|
`delphi_lsp` is the only supported import namespace. Update imports directly;
|
|
@@ -59,6 +59,17 @@ from .project_indexer import (
|
|
|
59
59
|
UnitInfo,
|
|
60
60
|
UnitParsedHook,
|
|
61
61
|
)
|
|
62
|
+
from .metrics import (
|
|
63
|
+
CyclomaticMetrics,
|
|
64
|
+
HalsteadMetrics,
|
|
65
|
+
LineMetrics,
|
|
66
|
+
MetricProblem,
|
|
67
|
+
ProjectMetrics,
|
|
68
|
+
RoutineComplexity,
|
|
69
|
+
UnitMetrics,
|
|
70
|
+
analyze_project,
|
|
71
|
+
analyze_unit,
|
|
72
|
+
)
|
|
62
73
|
|
|
63
74
|
__all__ = [
|
|
64
75
|
'__version__',
|
|
@@ -134,6 +145,15 @@ __all__ = [
|
|
|
134
145
|
'IncludeFileInfo',
|
|
135
146
|
'GetUnitSyntaxHook',
|
|
136
147
|
'UnitParsedHook',
|
|
148
|
+
'MetricProblem',
|
|
149
|
+
'LineMetrics',
|
|
150
|
+
'HalsteadMetrics',
|
|
151
|
+
'RoutineComplexity',
|
|
152
|
+
'CyclomaticMetrics',
|
|
153
|
+
'UnitMetrics',
|
|
154
|
+
'ProjectMetrics',
|
|
155
|
+
'analyze_unit',
|
|
156
|
+
'analyze_project',
|
|
137
157
|
'dumps',
|
|
138
158
|
'loads',
|
|
139
159
|
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.0.2"
|
|
@@ -44,6 +44,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
44
44
|
"implementation",
|
|
45
45
|
"references",
|
|
46
46
|
"problems",
|
|
47
|
+
"metrics",
|
|
47
48
|
],
|
|
48
49
|
)
|
|
49
50
|
view.add_argument("--query", default="")
|
|
@@ -66,11 +67,19 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
66
67
|
|
|
67
68
|
opencode = subcommands.add_parser("opencode", help="Install opencode integration.")
|
|
68
69
|
opencode_commands = opencode.add_subparsers(dest="opencode_command", required=True)
|
|
69
|
-
opencode_install = opencode_commands.add_parser(
|
|
70
|
+
opencode_install = opencode_commands.add_parser(
|
|
71
|
+
"install", help="Install the package-named skill, Markdown agent, and OpenCode plugin."
|
|
72
|
+
)
|
|
70
73
|
opencode_install.add_argument("--target", type=Path, default=Path("."))
|
|
71
74
|
opencode_install.add_argument("--python", default=sys.executable)
|
|
72
75
|
opencode_install.add_argument("--force", action="store_true")
|
|
73
|
-
opencode_install.add_argument(
|
|
76
|
+
opencode_install.add_argument(
|
|
77
|
+
"--write-agent",
|
|
78
|
+
"--write-config",
|
|
79
|
+
dest="write_config",
|
|
80
|
+
action="store_true",
|
|
81
|
+
help="Deprecated compatibility option; the Markdown agent is always installed and opencode.json is never touched.",
|
|
82
|
+
)
|
|
74
83
|
opencode_install.set_defaults(func=_opencode_install)
|
|
75
84
|
|
|
76
85
|
worker = subcommands.add_parser("worker", help="Serve Protocol v2 NDJSON requests.")
|
|
@@ -138,7 +147,7 @@ def _skill_install(args: argparse.Namespace) -> None:
|
|
|
138
147
|
|
|
139
148
|
|
|
140
149
|
def _opencode_install(args: argparse.Namespace) -> None:
|
|
141
|
-
skill_path, plugin_path,
|
|
150
|
+
skill_path, plugin_path, agent_path = install_opencode_support(
|
|
142
151
|
args.target,
|
|
143
152
|
python_executable=args.python,
|
|
144
153
|
force=args.force,
|
|
@@ -146,8 +155,7 @@ def _opencode_install(args: argparse.Namespace) -> None:
|
|
|
146
155
|
)
|
|
147
156
|
print(skill_path)
|
|
148
157
|
print(plugin_path)
|
|
149
|
-
|
|
150
|
-
print(config_path)
|
|
158
|
+
print(agent_path)
|
|
151
159
|
|
|
152
160
|
|
|
153
161
|
def _worker(args: argparse.Namespace) -> None:
|
|
@@ -18,14 +18,16 @@ from .agent_protocol import (
|
|
|
18
18
|
make_target_id,
|
|
19
19
|
paginate_items,
|
|
20
20
|
)
|
|
21
|
+
from .agent_metrics import build_workspace_metrics, project_metric_item, unit_metric_item
|
|
21
22
|
from .agent_relations import ProjectRelationIndex, RelationTarget
|
|
22
|
-
from .agent_workspace import
|
|
23
|
+
from .agent_workspace import AgentWorkspace, unit_display_path, unit_source_path, unit_target_id
|
|
23
24
|
from .consts import AttributeName, SyntaxNodeType
|
|
24
25
|
from .lsp_server import build_outline_semantic_model, multiline_string_block_end
|
|
25
26
|
from .nodes import CompoundSyntaxNode, SyntaxNode
|
|
26
27
|
from .parser import DelphiParser
|
|
27
28
|
from .semantic import Scope, ScopeKind, Symbol, SymbolKind
|
|
28
29
|
from .source_reader import read_source_text
|
|
30
|
+
from .metrics import ProjectMetrics
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
_ROUTINE_KINDS = frozenset(
|
|
@@ -238,6 +240,8 @@ class AgentContext:
|
|
|
238
240
|
self._last_revision = workspace.workspace_revision
|
|
239
241
|
self._registry: _Registry | None = None
|
|
240
242
|
self._relation_index: ProjectRelationIndex | None = None
|
|
243
|
+
self._metrics: ProjectMetrics | None = None
|
|
244
|
+
self._metrics_revision = ""
|
|
241
245
|
|
|
242
246
|
@classmethod
|
|
243
247
|
def open(
|
|
@@ -270,6 +274,8 @@ class AgentContext:
|
|
|
270
274
|
self._require_selected_project()
|
|
271
275
|
items = self._problem_items()
|
|
272
276
|
return self._response(parsed, revision, items)
|
|
277
|
+
if parsed.action == "metrics":
|
|
278
|
+
return self._handle_metrics(parsed, revision)
|
|
273
279
|
if parsed.action == "focus":
|
|
274
280
|
return self._handle_focus(parsed, revision)
|
|
275
281
|
if parsed.action == "find":
|
|
@@ -294,10 +300,14 @@ class AgentContext:
|
|
|
294
300
|
if current_project_id != previous_project_id:
|
|
295
301
|
self._registry = None
|
|
296
302
|
self._relation_index = None
|
|
303
|
+
self._metrics = None
|
|
304
|
+
self._metrics_revision = ""
|
|
297
305
|
self._focus = Focus(project_id=current_project_id) if current_project_id else Focus()
|
|
298
306
|
elif revision != self._last_revision:
|
|
299
307
|
self._registry = None
|
|
300
308
|
self._relation_index = None
|
|
309
|
+
self._metrics = None
|
|
310
|
+
self._metrics_revision = ""
|
|
301
311
|
elif self._focus.project_id != current_project_id:
|
|
302
312
|
self._focus = Focus(project_id=current_project_id) if current_project_id else Focus()
|
|
303
313
|
self._last_revision = revision
|
|
@@ -323,12 +333,11 @@ class AgentContext:
|
|
|
323
333
|
return items
|
|
324
334
|
|
|
325
335
|
for unit in self._workspace.units:
|
|
326
|
-
|
|
327
|
-
display_path = _stable_source_display_path(self._workspace.root, unit, source_path)
|
|
336
|
+
display_path = unit_display_path(self._workspace.root, unit)
|
|
328
337
|
items.append(
|
|
329
338
|
{
|
|
330
339
|
"item_type": "unit",
|
|
331
|
-
"unit_id":
|
|
340
|
+
"unit_id": unit_target_id(self._workspace.root, unit),
|
|
332
341
|
"name": unit.name,
|
|
333
342
|
"path": display_path,
|
|
334
343
|
"has_error": unit.has_error,
|
|
@@ -407,6 +416,55 @@ class AgentContext:
|
|
|
407
416
|
self._focus = Focus(project_id=self._workspace.active_project_id)
|
|
408
417
|
return self._response(request, revision, [self._focus.to_mapping()])
|
|
409
418
|
|
|
419
|
+
def _handle_metrics(self, request: AgentRequest, revision: str) -> AgentResponse:
|
|
420
|
+
if request.detail not in {"summary", "members"}:
|
|
421
|
+
raise AgentProtocolError(
|
|
422
|
+
"invalid_detail",
|
|
423
|
+
"Metrics supports only summary or members detail.",
|
|
424
|
+
)
|
|
425
|
+
metrics = self._require_metrics(revision)
|
|
426
|
+
detail = request.detail == "members"
|
|
427
|
+
if request.target_id:
|
|
428
|
+
unit = next(
|
|
429
|
+
(candidate for candidate in metrics.units if candidate.unit_id == request.target_id),
|
|
430
|
+
None,
|
|
431
|
+
)
|
|
432
|
+
if unit is None:
|
|
433
|
+
raise AgentProtocolError(
|
|
434
|
+
"target_not_found",
|
|
435
|
+
f"Target not found: {request.target_id}.",
|
|
436
|
+
)
|
|
437
|
+
return self._response(
|
|
438
|
+
request,
|
|
439
|
+
revision,
|
|
440
|
+
[unit_metric_item(unit, detail=detail)],
|
|
441
|
+
target_id=unit.unit_id,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
units = metrics.units
|
|
445
|
+
if request.query:
|
|
446
|
+
query = request.query.casefold()
|
|
447
|
+
units = tuple(
|
|
448
|
+
unit
|
|
449
|
+
for unit in units
|
|
450
|
+
if query in unit.name.casefold() or query in unit.path.casefold()
|
|
451
|
+
)
|
|
452
|
+
items = [unit_metric_item(unit, detail=detail) for unit in units]
|
|
453
|
+
else:
|
|
454
|
+
items = [
|
|
455
|
+
project_metric_item(metrics),
|
|
456
|
+
*(unit_metric_item(unit, detail=detail) for unit in units),
|
|
457
|
+
]
|
|
458
|
+
return self._response(request, revision, items)
|
|
459
|
+
|
|
460
|
+
def _require_metrics(self, revision: str) -> ProjectMetrics:
|
|
461
|
+
self._require_selected_project()
|
|
462
|
+
if self._metrics is not None and self._metrics_revision == revision:
|
|
463
|
+
return self._metrics
|
|
464
|
+
self._metrics = build_workspace_metrics(self._workspace)
|
|
465
|
+
self._metrics_revision = revision
|
|
466
|
+
return self._metrics
|
|
467
|
+
|
|
410
468
|
def _require_registry(self, revision: str) -> _Registry:
|
|
411
469
|
project_id = self._require_selected_project()
|
|
412
470
|
if (
|
|
@@ -608,8 +666,8 @@ def _build_registry(workspace: AgentWorkspace, project_id: str, revision: str) -
|
|
|
608
666
|
raw_symbols: list[_RawSymbol] = []
|
|
609
667
|
sources: dict[Path, _SourceDocument] = {}
|
|
610
668
|
for unit in workspace.units:
|
|
611
|
-
source_path =
|
|
612
|
-
display_path =
|
|
669
|
+
source_path = unit_source_path(workspace.root, unit)
|
|
670
|
+
display_path = unit_display_path(workspace.root, unit)
|
|
613
671
|
try:
|
|
614
672
|
text = read_source_text(source_path)
|
|
615
673
|
except OSError as exc:
|
|
@@ -727,22 +785,6 @@ def _build_registry(workspace: AgentWorkspace, project_id: str, revision: str) -
|
|
|
727
785
|
)
|
|
728
786
|
|
|
729
787
|
|
|
730
|
-
def _unit_source_path(root: Path, unit: AgentUnit) -> Path:
|
|
731
|
-
path = Path(unit.path)
|
|
732
|
-
if not path.is_absolute():
|
|
733
|
-
path = root / path
|
|
734
|
-
return path.expanduser().resolve()
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
def _stable_source_display_path(root: Path, unit: AgentUnit, source_path: Path) -> str:
|
|
738
|
-
try:
|
|
739
|
-
return source_path.relative_to(root).as_posix()
|
|
740
|
-
except ValueError:
|
|
741
|
-
unit_component = _stable_path_component(unit.name or source_path.stem)
|
|
742
|
-
file_component = _stable_path_component(source_path.name)
|
|
743
|
-
return f"@external/{unit_component}/{file_component}"
|
|
744
|
-
|
|
745
|
-
|
|
746
788
|
def _stable_path_component(value: str) -> str:
|
|
747
789
|
normalized = unicodedata.normalize("NFC", value).replace("\\", "_").replace("/", "_")
|
|
748
790
|
return normalized or "unknown"
|