lvkit 0.2.0__tar.gz → 0.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.
- {lvkit-0.2.0 → lvkit-0.3.0}/PKG-INFO +1 -1
- {lvkit-0.2.0 → lvkit-0.3.0}/pyproject.toml +1 -1
- lvkit-0.3.0/scripts/audit_unresolved.py +311 -0
- lvkit-0.3.0/scripts/dump_primitive_context.py +253 -0
- lvkit-0.3.0/scripts/gen_semantics_probe.py +363 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/scripts/generate_docs.py +20 -3
- {lvkit-0.2.0 → lvkit-0.3.0}/scripts/generate_python.py +10 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/__init__.py +1 -1
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/cli.py +84 -5
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/ast_utils.py +8 -2
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/builder.py +9 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/class_builder.py +3 -1
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/context.py +27 -1
- lvkit-0.3.0/src/lvkit/codegen/elementwise.py +127 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/nodes/__init__.py +9 -1
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/nodes/case.py +15 -1
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/nodes/compound.py +29 -7
- lvkit-0.3.0/src/lvkit/codegen/nodes/formula.py +117 -0
- lvkit-0.3.0/src/lvkit/codegen/nodes/in_place.py +261 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/nodes/primitive.py +39 -5
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/nodes/subvi.py +126 -40
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/stubs.py +1 -1
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/primitives.json +269 -49
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/docs/generate.py +4 -0
- lvkit-0.3.0/src/lvkit/extractor.py +245 -0
- lvkit-0.3.0/src/lvkit/formula/__init__.py +33 -0
- lvkit-0.3.0/src/lvkit/formula/cparser.py +325 -0
- lvkit-0.3.0/src/lvkit/formula/emit.py +533 -0
- lvkit-0.3.0/src/lvkit/formula/lexer.py +112 -0
- lvkit-0.3.0/src/lvkit/formula/nodes.py +119 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/graph/construction.py +268 -89
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/graph/core.py +83 -34
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/graph/describe.py +298 -40
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/graph/diff.py +242 -17
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/graph/loading.py +240 -12
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/graph/models.py +30 -1
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/graph/operations.py +74 -6
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/graph/queries.py +17 -8
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/labview_error_codes.py +1 -1
- lvkit-0.3.0/src/lvkit/mcp/__init__.py +5 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/mcp/schemas.py +0 -31
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/mcp/server.py +70 -77
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/mcp/tools.py +15 -64
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/models.py +28 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/constants.py +36 -2
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/front_panel.py +5 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/models.py +30 -9
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/node_types.py +297 -5
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/nodes/__init__.py +2 -0
- lvkit-0.3.0/src/lvkit/parser/nodes/decompose.py +74 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/nodes/sequence.py +1 -1
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/utils.py +2 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/vi.py +57 -33
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/pipeline.py +38 -15
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/primitive_resolver.py +6 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/project_store.py +14 -8
- lvkit-0.3.0/src/lvkit/runtime/__init__.py +1 -0
- lvkit-0.3.0/src/lvkit/runtime/lv.py +206 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/skill_templates/lvkit-convert/SKILL.md +5 -5
- lvkit-0.3.0/src/lvkit/skill_templates/lvkit-describe/SKILL.md +66 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/skill_templates/lvkit-idiomatic/SKILL.md +0 -19
- lvkit-0.3.0/tests/test_case_codegen.py +230 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_compound_codegen.py +129 -0
- lvkit-0.3.0/tests/test_dependency_path_refs.py +98 -0
- lvkit-0.3.0/tests/test_determinism.py +52 -0
- lvkit-0.3.0/tests/test_diff_wiring_denoise.py +184 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_e2e_codegen.py +78 -0
- lvkit-0.3.0/tests/test_elementwise.py +100 -0
- lvkit-0.3.0/tests/test_formula_codegen.py +126 -0
- lvkit-0.3.0/tests/test_formula_runtime.py +95 -0
- lvkit-0.3.0/tests/test_formula_semantics.py +198 -0
- lvkit-0.3.0/tests/test_formula_transpile.py +225 -0
- lvkit-0.3.0/tests/test_frame_attribution.py +172 -0
- lvkit-0.3.0/tests/test_in_place_codegen.py +506 -0
- lvkit-0.3.0/tests/test_lvpy.py +11 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_naming.py +5 -1
- lvkit-0.3.0/tests/test_primitive_resolutions.py +86 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_project_store.py +18 -1
- lvkit-0.2.0/scripts/analyze_vi.py +0 -75
- lvkit-0.2.0/src/lvkit/extractor.py +0 -98
- lvkit-0.2.0/src/lvkit/mcp/__init__.py +0 -7
- lvkit-0.2.0/src/lvkit/skill_templates/lvkit-describe/SKILL.md +0 -108
- lvkit-0.2.0/tests/test_lvpy.py +0 -8
- {lvkit-0.2.0 → lvkit-0.3.0}/.gitignore +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/.pre-commit-config.yaml +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/LICENSE +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/README.md +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/scripts/generate_driver_data.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/scripts/populate_vilib.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/scripts/sync_skills.sh +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/_data.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/README.md +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/__init__.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/ast_optimizer.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/condition_builder.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/dataflow.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/error_handler.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/expressions.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/fragment.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/function.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/imports.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/nodes/base.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/nodes/constant.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/nodes/invoke_node.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/nodes/loop.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/nodes/nmux.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/nodes/printf.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/nodes/property_node.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/nodes/sequence.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/codegen/unresolved.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/drivers/_index.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/drivers/daqmx.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/drivers/nidcpower.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/drivers/nidigital.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/drivers/nidmm.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/drivers/nifgen.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/drivers/niscope.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/drivers/niswitch.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/drivers/serial.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/drivers/visa.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/labview_error_codes.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/openg/_index.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/openg/array.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/openg/file.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/openg/string.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/openg/time.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/openg/variant.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/primitives-from-pdf.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/vilib/_index.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/vilib/_pending_terminals.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/vilib/_types.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/vilib/application-control.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/vilib/array.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/vilib/boolean.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/vilib/cluster.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/vilib/comparison.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/vilib/error-handling.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/vilib/file-io.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/vilib/numeric.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/vilib/other.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/vilib/string.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/vilib/structures.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/data/vilib/variant.json +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/docs/__init__.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/docs/html_generator.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/docs/template.css +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/docs/utils.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/graph/__init__.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/graph/analysis.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/graph/flowchart.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/labview_error.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/__init__.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/defaults.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/flags.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/metadata.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/naming.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/nodes/base.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/nodes/case.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/nodes/constant.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/nodes/loop.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/type_mapping.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/parser/type_resolution.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/py.typed +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/skill_templates/__init__.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/skill_templates/lvkit-resolve-primitive/SKILL.md +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/skill_templates/lvkit-resolve-vilib/SKILL.md +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/structure.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/terminal_collector.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/type_defaults.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/src/lvkit/vilib_resolver.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/__init__.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/conftest.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/helpers.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_ast_builder.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_ast_optimizer.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_case_parser.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_codegen_context.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_condition_builder.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_constant_decoding.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_diff.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_driver_codegen.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_dynamic_dispatch.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_error_codegen.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_error_handling.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_graceful_degradation.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_loop_codegen.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_new_codegen.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_parallel_codegen.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_parser.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_parser_regression.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_sequence.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_soft_codegen.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_type_driven_fixes.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_type_fields.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_type_loading.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_type_mapping.py +0 -0
- {lvkit-0.2.0 → lvkit-0.3.0}/tests/test_vi_graph.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lvkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Understand and convert LabVIEW VIs to Python without a LabVIEW license
|
|
5
5
|
Project-URL: Repository, https://github.com/pragmatest-dev/lvkit
|
|
6
6
|
Project-URL: Issues, https://github.com/pragmatest-dev/lvkit/issues
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Audit primitives and vilib VIs referenced in samples but not in our data.
|
|
3
|
+
|
|
4
|
+
Walks samples/, parses each VI via XML pairs, cross-references primitives and
|
|
5
|
+
vilib dependencies against the JSON data files, and emits a markdown report
|
|
6
|
+
to .tmp/audit-unresolved.md sorted by usage count (highest first).
|
|
7
|
+
|
|
8
|
+
Usage: uv run python scripts/audit_unresolved.py
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from collections import defaultdict
|
|
14
|
+
from datetime import date
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from lvkit.parser import parse_vi
|
|
18
|
+
from lvkit.parser.node_types import PrimitiveNode
|
|
19
|
+
from lvkit.primitive_resolver import PrimitiveResolver
|
|
20
|
+
from lvkit.vilib_resolver import VIEntry, VILibResolver
|
|
21
|
+
|
|
22
|
+
REPO_ROOT = Path(__file__).parent.parent
|
|
23
|
+
SAMPLES = REPO_ROOT / "samples"
|
|
24
|
+
REPORT_PATH = REPO_ROOT / ".tmp" / "audit-unresolved.md"
|
|
25
|
+
|
|
26
|
+
# Status buckets
|
|
27
|
+
CAPTURED = "captured"
|
|
28
|
+
PARTIAL = "partial"
|
|
29
|
+
MISSING = "missing"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def find_bd_xml_pairs(samples_root: Path) -> list[tuple[Path, Path | None]]:
|
|
33
|
+
"""Find all (bd_xml, main_xml) pairs under samples/.
|
|
34
|
+
|
|
35
|
+
Returns a list of (bd_xml, main_xml) tuples. main_xml is optional.
|
|
36
|
+
Dedups by bd_xml absolute path.
|
|
37
|
+
"""
|
|
38
|
+
seen: set[Path] = set()
|
|
39
|
+
pairs: list[tuple[Path, Path | None]] = []
|
|
40
|
+
for bd_xml in samples_root.rglob("*_BDHb.xml"):
|
|
41
|
+
resolved = bd_xml.resolve()
|
|
42
|
+
if resolved in seen:
|
|
43
|
+
continue
|
|
44
|
+
seen.add(resolved)
|
|
45
|
+
# Derive main_xml path: same stem minus _BDHb
|
|
46
|
+
main_xml_name = bd_xml.name.replace("_BDHb.xml", ".xml")
|
|
47
|
+
main_xml = bd_xml.parent / main_xml_name
|
|
48
|
+
if not main_xml.exists():
|
|
49
|
+
main_xml = None
|
|
50
|
+
pairs.append((bd_xml, main_xml))
|
|
51
|
+
return pairs
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def classify_primitive(entry: dict | None) -> str:
|
|
55
|
+
"""Classify a primitive entry as captured/partial/missing."""
|
|
56
|
+
if entry is None:
|
|
57
|
+
return MISSING
|
|
58
|
+
if entry.get("placeholder"):
|
|
59
|
+
return PARTIAL
|
|
60
|
+
python_code = entry.get("python_code")
|
|
61
|
+
if not python_code:
|
|
62
|
+
return PARTIAL
|
|
63
|
+
terminals = entry.get("terminals") or []
|
|
64
|
+
if not terminals:
|
|
65
|
+
return PARTIAL
|
|
66
|
+
for t in terminals:
|
|
67
|
+
if t.get("index") is None:
|
|
68
|
+
return PARTIAL
|
|
69
|
+
return CAPTURED
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def classify_vilib(entry: VIEntry | None) -> str:
|
|
73
|
+
"""Classify a vilib VI entry as captured/partial/missing."""
|
|
74
|
+
if entry is None:
|
|
75
|
+
return MISSING
|
|
76
|
+
if entry.status == "needs_terminals":
|
|
77
|
+
return PARTIAL
|
|
78
|
+
for t in entry.terminals:
|
|
79
|
+
if t.index is None:
|
|
80
|
+
return PARTIAL
|
|
81
|
+
return CAPTURED
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def main() -> None:
|
|
85
|
+
prim_resolver = PrimitiveResolver()
|
|
86
|
+
vilib_resolver = VILibResolver()
|
|
87
|
+
|
|
88
|
+
# prim_id -> {"count": int, "name": str, "vis": set[str]}
|
|
89
|
+
prim_usage: dict[str, dict] = defaultdict(
|
|
90
|
+
lambda: {"count": 0, "name": None, "vis": set()}
|
|
91
|
+
)
|
|
92
|
+
# vilib_path -> {"count": int, "name": str, "vis": set[str]}
|
|
93
|
+
vilib_usage: dict[str, dict] = defaultdict(
|
|
94
|
+
lambda: {"count": 0, "name": None, "vis": set()}
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
pairs = find_bd_xml_pairs(SAMPLES)
|
|
98
|
+
print(f"Scanning {len(pairs)} VIs from {SAMPLES}...")
|
|
99
|
+
|
|
100
|
+
parse_failures: list[tuple[Path, str]] = []
|
|
101
|
+
for bd_xml, main_xml in pairs:
|
|
102
|
+
try:
|
|
103
|
+
parsed = parse_vi(bd_xml=bd_xml, main_xml=main_xml)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
parse_failures.append((bd_xml, f"{type(e).__name__}: {e}"))
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
vi_label = bd_xml.name.replace("_BDHb.xml", "")
|
|
109
|
+
|
|
110
|
+
# Primitives pass
|
|
111
|
+
for node in parsed.block_diagram.nodes:
|
|
112
|
+
if not isinstance(node, PrimitiveNode):
|
|
113
|
+
continue
|
|
114
|
+
if node.prim_res_id is None:
|
|
115
|
+
continue
|
|
116
|
+
key = str(node.prim_res_id)
|
|
117
|
+
prim_usage[key]["count"] += 1
|
|
118
|
+
prim_usage[key]["vis"].add(vi_label)
|
|
119
|
+
if node.name and prim_usage[key]["name"] is None:
|
|
120
|
+
prim_usage[key]["name"] = node.name
|
|
121
|
+
|
|
122
|
+
# VIlib pass (requires main_xml)
|
|
123
|
+
for ref in parsed.metadata.dependency_refs:
|
|
124
|
+
if not ref.path_tokens or ref.path_tokens[0] != "<vilib>":
|
|
125
|
+
continue
|
|
126
|
+
vilib_path = "/".join(ref.path_tokens[1:])
|
|
127
|
+
vilib_usage[vilib_path]["count"] += 1
|
|
128
|
+
vilib_usage[vilib_path]["vis"].add(vi_label)
|
|
129
|
+
if vilib_usage[vilib_path]["name"] is None:
|
|
130
|
+
vilib_usage[vilib_path]["name"] = ref.name
|
|
131
|
+
|
|
132
|
+
# Classify primitives
|
|
133
|
+
prim_buckets: dict[str, list] = {CAPTURED: [], PARTIAL: [], MISSING: []}
|
|
134
|
+
for prim_id, info in prim_usage.items():
|
|
135
|
+
entry = prim_resolver.get_by_id(prim_id)
|
|
136
|
+
status = classify_primitive(entry)
|
|
137
|
+
name = (entry or {}).get("name") or info["name"] or "Unknown"
|
|
138
|
+
prim_buckets[status].append({
|
|
139
|
+
"id": prim_id,
|
|
140
|
+
"name": name,
|
|
141
|
+
"count": info["count"],
|
|
142
|
+
"vis": sorted(info["vis"]),
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
# Classify vilib
|
|
146
|
+
vilib_buckets: dict[str, list] = {CAPTURED: [], PARTIAL: [], MISSING: []}
|
|
147
|
+
for path, info in vilib_usage.items():
|
|
148
|
+
entry = vilib_resolver.resolve(path)
|
|
149
|
+
if entry is None and info["name"]:
|
|
150
|
+
entry = vilib_resolver.resolve_by_name(info["name"])
|
|
151
|
+
status = classify_vilib(entry)
|
|
152
|
+
name = (entry.name if entry else info["name"]) or "Unknown"
|
|
153
|
+
vilib_buckets[status].append({
|
|
154
|
+
"path": path,
|
|
155
|
+
"name": name,
|
|
156
|
+
"count": info["count"],
|
|
157
|
+
"vis": sorted(info["vis"]),
|
|
158
|
+
"status": entry.status if entry else None,
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
# Sort by count desc
|
|
162
|
+
for bucket in prim_buckets.values():
|
|
163
|
+
bucket.sort(key=lambda x: -x["count"])
|
|
164
|
+
for bucket in vilib_buckets.values():
|
|
165
|
+
bucket.sort(key=lambda x: -x["count"])
|
|
166
|
+
|
|
167
|
+
# JSON-level audit: count all entries in vilib JSON (not just sample refs).
|
|
168
|
+
# Each entry appears under multiple keys in _by_name — dedup by object id.
|
|
169
|
+
seen_ids: set[int] = set()
|
|
170
|
+
json_vilib_total = 0
|
|
171
|
+
json_vilib_captured = 0
|
|
172
|
+
json_vilib_needs_terminals = 0
|
|
173
|
+
json_vilib_missing_idx = 0
|
|
174
|
+
for entry in vilib_resolver._by_name.values():
|
|
175
|
+
if id(entry) in seen_ids:
|
|
176
|
+
continue
|
|
177
|
+
seen_ids.add(id(entry))
|
|
178
|
+
json_vilib_total += 1
|
|
179
|
+
if entry.status == "needs_terminals":
|
|
180
|
+
json_vilib_needs_terminals += 1
|
|
181
|
+
elif any(t.index is None for t in entry.terminals):
|
|
182
|
+
json_vilib_missing_idx += 1
|
|
183
|
+
else:
|
|
184
|
+
json_vilib_captured += 1
|
|
185
|
+
|
|
186
|
+
json_prim_total = len(prim_resolver.get_all_ids())
|
|
187
|
+
json_prim_captured = sum(
|
|
188
|
+
1 for pid in prim_resolver.get_all_ids()
|
|
189
|
+
if classify_primitive(prim_resolver.get_by_id(pid)) == CAPTURED
|
|
190
|
+
)
|
|
191
|
+
json_prim_partial = json_prim_total - json_prim_captured
|
|
192
|
+
|
|
193
|
+
# Write report
|
|
194
|
+
REPORT_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
195
|
+
lines: list[str] = []
|
|
196
|
+
lines.append(f"# Unresolved Audit — {date.today().isoformat()}\n")
|
|
197
|
+
lines.append(f"Scanned {len(pairs)} VI block diagrams from `samples/`.\n")
|
|
198
|
+
|
|
199
|
+
lines.append("## Summary (sample usage)\n")
|
|
200
|
+
lines.append(
|
|
201
|
+
f"- **Primitives**: "
|
|
202
|
+
f"{len(prim_buckets[CAPTURED])} captured / "
|
|
203
|
+
f"{len(prim_buckets[PARTIAL])} partial / "
|
|
204
|
+
f"{len(prim_buckets[MISSING])} missing "
|
|
205
|
+
f"({len(prim_usage)} unique primResIDs seen)"
|
|
206
|
+
)
|
|
207
|
+
lines.append(
|
|
208
|
+
f"- **VIlib**: "
|
|
209
|
+
f"{len(vilib_buckets[CAPTURED])} captured / "
|
|
210
|
+
f"{len(vilib_buckets[PARTIAL])} partial / "
|
|
211
|
+
f"{len(vilib_buckets[MISSING])} missing "
|
|
212
|
+
f"({len(vilib_usage)} unique paths seen)"
|
|
213
|
+
)
|
|
214
|
+
lines.append("")
|
|
215
|
+
lines.append("## JSON data state (all entries, regardless of samples)\n")
|
|
216
|
+
lines.append(
|
|
217
|
+
f"- **Primitives JSON**: {json_prim_total} total — "
|
|
218
|
+
f"{json_prim_captured} captured / {json_prim_partial} partial"
|
|
219
|
+
)
|
|
220
|
+
lines.append(
|
|
221
|
+
f"- **VIlib JSON**: {json_vilib_total} total — "
|
|
222
|
+
f"{json_vilib_captured} captured / "
|
|
223
|
+
f"{json_vilib_needs_terminals} needs_terminals / "
|
|
224
|
+
f"{json_vilib_missing_idx} missing indices"
|
|
225
|
+
)
|
|
226
|
+
if parse_failures:
|
|
227
|
+
lines.append(f"- **Parse failures**: {len(parse_failures)}")
|
|
228
|
+
lines.append("")
|
|
229
|
+
|
|
230
|
+
def _sample_vis(vis: list[str], limit: int = 3) -> str:
|
|
231
|
+
if len(vis) <= limit:
|
|
232
|
+
return ", ".join(vis)
|
|
233
|
+
return ", ".join(vis[:limit]) + f" (+{len(vis) - limit} more)"
|
|
234
|
+
|
|
235
|
+
# Missing primitives
|
|
236
|
+
lines.append("## Missing Primitives\n")
|
|
237
|
+
lines.append("| primResID | Name | Count | Sample VIs |")
|
|
238
|
+
lines.append("|-----------|------|-------|------------|")
|
|
239
|
+
for p in prim_buckets[MISSING]:
|
|
240
|
+
lines.append(
|
|
241
|
+
f"| {p['id']} | {p['name']} | {p['count']} | {_sample_vis(p['vis'])} |"
|
|
242
|
+
)
|
|
243
|
+
if not prim_buckets[MISSING]:
|
|
244
|
+
lines.append("| _(none)_ | | | |")
|
|
245
|
+
lines.append("")
|
|
246
|
+
|
|
247
|
+
# Partial primitives
|
|
248
|
+
lines.append("## Partial Primitives (needs terminals or python_code)\n")
|
|
249
|
+
lines.append("| primResID | Name | Count | Sample VIs |")
|
|
250
|
+
lines.append("|-----------|------|-------|------------|")
|
|
251
|
+
for p in prim_buckets[PARTIAL]:
|
|
252
|
+
lines.append(
|
|
253
|
+
f"| {p['id']} | {p['name']} | {p['count']} | {_sample_vis(p['vis'])} |"
|
|
254
|
+
)
|
|
255
|
+
if not prim_buckets[PARTIAL]:
|
|
256
|
+
lines.append("| _(none)_ | | | |")
|
|
257
|
+
lines.append("")
|
|
258
|
+
|
|
259
|
+
# Missing vilib
|
|
260
|
+
lines.append("## Missing VIlib VIs\n")
|
|
261
|
+
lines.append("| Path | Count | Sample VIs |")
|
|
262
|
+
lines.append("|------|-------|------------|")
|
|
263
|
+
for v in vilib_buckets[MISSING]:
|
|
264
|
+
lines.append(
|
|
265
|
+
f"| `{v['path']}` | {v['count']} | {_sample_vis(v['vis'])} |"
|
|
266
|
+
)
|
|
267
|
+
if not vilib_buckets[MISSING]:
|
|
268
|
+
lines.append("| _(none)_ | | |")
|
|
269
|
+
lines.append("")
|
|
270
|
+
|
|
271
|
+
# Partial vilib
|
|
272
|
+
lines.append("## Partial VIlib VIs (needs terminal indices)\n")
|
|
273
|
+
lines.append("| Path | Status | Count | Sample VIs |")
|
|
274
|
+
lines.append("|------|--------|-------|------------|")
|
|
275
|
+
for v in vilib_buckets[PARTIAL]:
|
|
276
|
+
lines.append(
|
|
277
|
+
f"| `{v['path']}` | {v['status']} | {v['count']} | "
|
|
278
|
+
f"{_sample_vis(v['vis'])} |"
|
|
279
|
+
)
|
|
280
|
+
if not vilib_buckets[PARTIAL]:
|
|
281
|
+
lines.append("| _(none)_ | | | |")
|
|
282
|
+
lines.append("")
|
|
283
|
+
|
|
284
|
+
# Parse failures (if any)
|
|
285
|
+
if parse_failures:
|
|
286
|
+
lines.append("## Parse Failures\n")
|
|
287
|
+
for path, err in parse_failures[:30]:
|
|
288
|
+
rel = path.relative_to(REPO_ROOT)
|
|
289
|
+
lines.append(f"- `{rel}` — {err}")
|
|
290
|
+
if len(parse_failures) > 30:
|
|
291
|
+
lines.append(f"- _(+{len(parse_failures) - 30} more)_")
|
|
292
|
+
lines.append("")
|
|
293
|
+
|
|
294
|
+
REPORT_PATH.write_text("\n".join(lines))
|
|
295
|
+
print(f"Report written to {REPORT_PATH.relative_to(REPO_ROOT)}")
|
|
296
|
+
print(
|
|
297
|
+
f" Primitives: {len(prim_buckets[CAPTURED])} captured / "
|
|
298
|
+
f"{len(prim_buckets[PARTIAL])} partial / "
|
|
299
|
+
f"{len(prim_buckets[MISSING])} missing"
|
|
300
|
+
)
|
|
301
|
+
print(
|
|
302
|
+
f" VIlib: {len(vilib_buckets[CAPTURED])} captured / "
|
|
303
|
+
f"{len(vilib_buckets[PARTIAL])} partial / "
|
|
304
|
+
f"{len(vilib_buckets[MISSING])} missing"
|
|
305
|
+
)
|
|
306
|
+
if parse_failures:
|
|
307
|
+
print(f" Parse failures: {len(parse_failures)}")
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
if __name__ == "__main__":
|
|
311
|
+
main()
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Dump PrimitiveResolutionNeeded-shaped context for missing primitives.
|
|
3
|
+
|
|
4
|
+
For each primResID seen in samples but not captured in primitives.json,
|
|
5
|
+
emit a markdown section matching the PrimitiveResolutionNeeded message
|
|
6
|
+
format (terminal indices, directions, types, usage count, sample VIs).
|
|
7
|
+
|
|
8
|
+
The output feeds the `lvkit-resolve-primitive` skill — one section per
|
|
9
|
+
primitive, sorted by usage count descending.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
uv run python scripts/dump_primitive_context.py [--limit 25]
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
from collections import defaultdict
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
from lvkit.parser import parse_vi
|
|
23
|
+
from lvkit.parser.node_types import PrimitiveNode
|
|
24
|
+
from lvkit.primitive_resolver import PrimitiveResolver
|
|
25
|
+
|
|
26
|
+
REPO_ROOT = Path(__file__).parent.parent
|
|
27
|
+
SAMPLES = REPO_ROOT / "samples"
|
|
28
|
+
REPORT_PATH = REPO_ROOT / ".tmp" / "missing-primitives-context.md"
|
|
29
|
+
|
|
30
|
+
# primResID range → hint (copied from SKILL.md Step 4)
|
|
31
|
+
RANGE_HINTS: list[tuple[int, int, str]] = [
|
|
32
|
+
(1044, 1064, "Array operations"),
|
|
33
|
+
(1061, 1081, "Numeric/arithmetic"),
|
|
34
|
+
(1083, 1128, "Path/comparison/boolean"),
|
|
35
|
+
(1140, 1170, "Type conversion, variant, data manipulation"),
|
|
36
|
+
(1300, 1340, "Timing, constants, clusters"),
|
|
37
|
+
(1419, 1435, "Path operations"),
|
|
38
|
+
(1500, 1540, "String operations"),
|
|
39
|
+
(1600, 1610, "Flatten/unflatten"),
|
|
40
|
+
(1809, 1911, "Array index/sort/delete"),
|
|
41
|
+
(1999, 1999, "Path constant"),
|
|
42
|
+
(2073, 2076, "Error handling"),
|
|
43
|
+
(2401, 2401, "Merge Errors"),
|
|
44
|
+
(8003, 8083, "File I/O"),
|
|
45
|
+
(8100, 8101, "VI info"),
|
|
46
|
+
(8201, 8205, "Variant operations"),
|
|
47
|
+
(9000, 9114, "VI Server, references, scripting"),
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def range_hint(prim_id: int) -> str:
|
|
52
|
+
for lo, hi, hint in RANGE_HINTS:
|
|
53
|
+
if lo <= prim_id <= hi:
|
|
54
|
+
return f"{lo}-{hi}: {hint}"
|
|
55
|
+
return "(no range hint)"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class TerminalObservation:
|
|
60
|
+
index: int | None
|
|
61
|
+
direction: str
|
|
62
|
+
type_name: str
|
|
63
|
+
name: str | None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class Invocation:
|
|
68
|
+
vi_label: str
|
|
69
|
+
bd_xml: Path
|
|
70
|
+
terminals: list[TerminalObservation]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def find_bd_xml_pairs(samples_root: Path) -> list[tuple[Path, Path | None]]:
|
|
74
|
+
seen: set[Path] = set()
|
|
75
|
+
pairs: list[tuple[Path, Path | None]] = []
|
|
76
|
+
for bd_xml in samples_root.rglob("*_BDHb.xml"):
|
|
77
|
+
resolved = bd_xml.resolve()
|
|
78
|
+
if resolved in seen:
|
|
79
|
+
continue
|
|
80
|
+
seen.add(resolved)
|
|
81
|
+
main_xml = bd_xml.parent / bd_xml.name.replace("_BDHb.xml", ".xml")
|
|
82
|
+
if not main_xml.exists():
|
|
83
|
+
main_xml = None
|
|
84
|
+
pairs.append((bd_xml, main_xml))
|
|
85
|
+
return pairs
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def collect_invocations(
|
|
89
|
+
pairs: list[tuple[Path, Path | None]],
|
|
90
|
+
missing_ids: set[int],
|
|
91
|
+
max_per_id: int,
|
|
92
|
+
) -> dict[int, tuple[int, list[Invocation]]]:
|
|
93
|
+
"""Scan all pairs, collect up to max_per_id invocations per primResID.
|
|
94
|
+
|
|
95
|
+
Returns: prim_id -> (total_count, invocations)
|
|
96
|
+
"""
|
|
97
|
+
counts: dict[int, int] = defaultdict(int)
|
|
98
|
+
invocations: dict[int, list[Invocation]] = defaultdict(list)
|
|
99
|
+
|
|
100
|
+
for bd_xml, main_xml in pairs:
|
|
101
|
+
try:
|
|
102
|
+
parsed = parse_vi(bd_xml=bd_xml, main_xml=main_xml)
|
|
103
|
+
except Exception:
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
vi_label = bd_xml.name.replace("_BDHb.xml", "")
|
|
107
|
+
for node in parsed.block_diagram.nodes:
|
|
108
|
+
if not isinstance(node, PrimitiveNode):
|
|
109
|
+
continue
|
|
110
|
+
if node.prim_res_id not in missing_ids:
|
|
111
|
+
continue
|
|
112
|
+
counts[node.prim_res_id] += 1
|
|
113
|
+
|
|
114
|
+
# Only record detailed invocations up to max_per_id
|
|
115
|
+
if len(invocations[node.prim_res_id]) >= max_per_id:
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
terms: list[TerminalObservation] = []
|
|
119
|
+
for _uid, t_info in parsed.block_diagram.terminal_info.items():
|
|
120
|
+
if t_info.parent_uid != node.uid:
|
|
121
|
+
continue
|
|
122
|
+
type_name = (
|
|
123
|
+
t_info.parsed_type.type_name
|
|
124
|
+
if t_info.parsed_type else "?"
|
|
125
|
+
)
|
|
126
|
+
terms.append(TerminalObservation(
|
|
127
|
+
index=t_info.index,
|
|
128
|
+
direction="output" if t_info.is_output else "input",
|
|
129
|
+
type_name=type_name,
|
|
130
|
+
name=t_info.name,
|
|
131
|
+
))
|
|
132
|
+
terms.sort(key=lambda t: (t.index is None, t.index or 0))
|
|
133
|
+
invocations[node.prim_res_id].append(Invocation(
|
|
134
|
+
vi_label=vi_label,
|
|
135
|
+
bd_xml=bd_xml,
|
|
136
|
+
terminals=terms,
|
|
137
|
+
))
|
|
138
|
+
|
|
139
|
+
return {pid: (counts[pid], invs) for pid, invs in invocations.items()}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def main() -> None:
|
|
143
|
+
ap = argparse.ArgumentParser()
|
|
144
|
+
ap.add_argument(
|
|
145
|
+
"--limit", type=int, default=25,
|
|
146
|
+
help="Number of missing primitives to include (top N by usage)",
|
|
147
|
+
)
|
|
148
|
+
ap.add_argument(
|
|
149
|
+
"--invocations", type=int, default=3,
|
|
150
|
+
help="Max sample invocations per primResID",
|
|
151
|
+
)
|
|
152
|
+
args = ap.parse_args()
|
|
153
|
+
|
|
154
|
+
resolver = PrimitiveResolver()
|
|
155
|
+
|
|
156
|
+
# First pass: count all primResIDs in samples to build the missing set
|
|
157
|
+
# sorted by count. Reuse audit logic: classify missing vs captured via
|
|
158
|
+
# resolver.get_by_id + python_code/terminals check.
|
|
159
|
+
pairs = find_bd_xml_pairs(SAMPLES)
|
|
160
|
+
print(f"Scanning {len(pairs)} VIs...")
|
|
161
|
+
|
|
162
|
+
all_counts: dict[int, int] = defaultdict(int)
|
|
163
|
+
for bd_xml, main_xml in pairs:
|
|
164
|
+
try:
|
|
165
|
+
parsed = parse_vi(bd_xml=bd_xml, main_xml=main_xml)
|
|
166
|
+
except Exception:
|
|
167
|
+
continue
|
|
168
|
+
for node in parsed.block_diagram.nodes:
|
|
169
|
+
if isinstance(node, PrimitiveNode) and node.prim_res_id is not None:
|
|
170
|
+
all_counts[node.prim_res_id] += 1
|
|
171
|
+
|
|
172
|
+
# Filter to missing primitives
|
|
173
|
+
missing: list[tuple[int, int]] = []
|
|
174
|
+
for pid, count in all_counts.items():
|
|
175
|
+
entry = resolver.get_by_id(str(pid))
|
|
176
|
+
if entry is None:
|
|
177
|
+
missing.append((pid, count))
|
|
178
|
+
continue
|
|
179
|
+
# Partial: no python_code or any terminal missing index
|
|
180
|
+
if not entry.get("python_code"):
|
|
181
|
+
missing.append((pid, count))
|
|
182
|
+
continue
|
|
183
|
+
terms = entry.get("terminals") or []
|
|
184
|
+
if not terms or any(t.get("index") is None for t in terms):
|
|
185
|
+
missing.append((pid, count))
|
|
186
|
+
|
|
187
|
+
missing.sort(key=lambda x: -x[1])
|
|
188
|
+
top = missing[:args.limit]
|
|
189
|
+
print(f"Found {len(missing)} missing/partial primitives, taking top {len(top)}")
|
|
190
|
+
|
|
191
|
+
# Second pass: collect invocation details only for the top N
|
|
192
|
+
top_ids = {pid for pid, _ in top}
|
|
193
|
+
details = collect_invocations(pairs, top_ids, args.invocations)
|
|
194
|
+
|
|
195
|
+
# Write the report
|
|
196
|
+
REPORT_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
197
|
+
lines: list[str] = []
|
|
198
|
+
lines.append("# Missing primitives — resolution context\n")
|
|
199
|
+
lines.append(
|
|
200
|
+
f"Top {len(top)} of {len(missing)} missing/partial primitives "
|
|
201
|
+
f"by usage count across {len(pairs)} sample VIs.\n"
|
|
202
|
+
)
|
|
203
|
+
lines.append(
|
|
204
|
+
"Each section matches the `PrimitiveResolutionNeeded` diagnostic "
|
|
205
|
+
"format. Feed one section at a time to the "
|
|
206
|
+
"`lvkit-resolve-primitive` skill.\n"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
for pid, count in top:
|
|
210
|
+
invs = details.get(pid, (count, []))[1]
|
|
211
|
+
lines.append(f"## primResID {pid} — {count} uses\n")
|
|
212
|
+
lines.append(f"**Range hint**: {range_hint(pid)}\n")
|
|
213
|
+
|
|
214
|
+
existing = resolver.get_by_id(str(pid))
|
|
215
|
+
if existing is not None:
|
|
216
|
+
status = "partial"
|
|
217
|
+
reason = []
|
|
218
|
+
if not existing.get("python_code"):
|
|
219
|
+
reason.append("no python_code")
|
|
220
|
+
terms = existing.get("terminals") or []
|
|
221
|
+
if not terms:
|
|
222
|
+
reason.append("no terminals")
|
|
223
|
+
elif any(t.get("index") is None for t in terms):
|
|
224
|
+
reason.append("missing terminal indices")
|
|
225
|
+
lines.append(
|
|
226
|
+
f"**Current entry**: {existing.get('name', '?')} "
|
|
227
|
+
f"({status}: {', '.join(reason)})\n"
|
|
228
|
+
)
|
|
229
|
+
else:
|
|
230
|
+
lines.append("**Current entry**: (none)\n")
|
|
231
|
+
|
|
232
|
+
for i, inv in enumerate(invs, 1):
|
|
233
|
+
lines.append(f"### Sample {i}: {inv.vi_label}")
|
|
234
|
+
rel = inv.bd_xml.relative_to(REPO_ROOT)
|
|
235
|
+
lines.append(f"File: `{rel}`")
|
|
236
|
+
lines.append("Wired terminals from graph:")
|
|
237
|
+
for t in inv.terminals:
|
|
238
|
+
parts = [
|
|
239
|
+
f"index={t.index}",
|
|
240
|
+
f"direction={t.direction}",
|
|
241
|
+
f"type={t.type_name}",
|
|
242
|
+
]
|
|
243
|
+
if t.name:
|
|
244
|
+
parts.append(f"name={t.name}")
|
|
245
|
+
lines.append(f"- {' '.join(parts)}")
|
|
246
|
+
lines.append("")
|
|
247
|
+
|
|
248
|
+
REPORT_PATH.write_text("\n".join(lines))
|
|
249
|
+
print(f"Report written to {REPORT_PATH.relative_to(REPO_ROOT)}")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
if __name__ == "__main__":
|
|
253
|
+
main()
|