loomweave-plugin-python 1.5.1__py3-none-any.whl → 1.6.0__py3-none-any.whl
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.
- loomweave_plugin_python/__init__.py +1 -1
- loomweave_plugin_python/call_resolver.py +3 -0
- loomweave_plugin_python/extractor.py +4 -0
- loomweave_plugin_python/pyright_session.py +323 -40
- loomweave_plugin_python/server.py +5 -1
- {loomweave_plugin_python-1.5.1.data → loomweave_plugin_python-1.6.0.data}/data/share/loomweave/plugins/python/plugin.toml +7 -3
- {loomweave_plugin_python-1.5.1.dist-info → loomweave_plugin_python-1.6.0.dist-info}/METADATA +1 -1
- loomweave_plugin_python-1.6.0.dist-info/RECORD +18 -0
- loomweave_plugin_python-1.5.1.dist-info/RECORD +0 -18
- {loomweave_plugin_python-1.5.1.dist-info → loomweave_plugin_python-1.6.0.dist-info}/WHEEL +0 -0
- {loomweave_plugin_python-1.5.1.dist-info → loomweave_plugin_python-1.6.0.dist-info}/entry_points.txt +0 -0
|
@@ -115,6 +115,9 @@ class FacetCoverage:
|
|
|
115
115
|
class CallResolutionResult:
|
|
116
116
|
edges: list[CallsRawEdge] = field(default_factory=list)
|
|
117
117
|
unresolved_call_sites_total: int = 0
|
|
118
|
+
# Bare unshadowed-builtin calls dropped before persistence; disclosed so
|
|
119
|
+
# the omission is visible in run stats (clarion-8a862d8f7e).
|
|
120
|
+
unresolved_call_sites_skipped_builtin_total: int = 0
|
|
118
121
|
unresolved_call_sites: list[UnresolvedCallSite] = field(default_factory=list)
|
|
119
122
|
pyright_query_latency_ms: list[int] = field(default_factory=list)
|
|
120
123
|
pyright_index_parse_latency_ms: list[int] = field(default_factory=list)
|
|
@@ -260,6 +260,7 @@ class ImportsEdgeProperties(TypedDict):
|
|
|
260
260
|
@dataclass
|
|
261
261
|
class ExtractionStats:
|
|
262
262
|
unresolved_call_sites_total: int = 0
|
|
263
|
+
unresolved_call_sites_skipped_builtin_total: int = 0
|
|
263
264
|
unresolved_call_sites: list[UnresolvedCallSite] = field(default_factory=list)
|
|
264
265
|
reference_sites_total: int = 0
|
|
265
266
|
references_resolved_total: int = 0
|
|
@@ -307,6 +308,9 @@ class ExtractionStats:
|
|
|
307
308
|
calls_coverage=calls.coverage,
|
|
308
309
|
references_coverage=references.coverage,
|
|
309
310
|
unresolved_call_sites_total=calls.unresolved_call_sites_total,
|
|
311
|
+
unresolved_call_sites_skipped_builtin_total=(
|
|
312
|
+
calls.unresolved_call_sites_skipped_builtin_total
|
|
313
|
+
),
|
|
310
314
|
unresolved_call_sites=calls.unresolved_call_sites,
|
|
311
315
|
reference_sites_total=references.reference_sites_total,
|
|
312
316
|
references_resolved_total=references.references_resolved_total,
|
|
@@ -14,7 +14,12 @@ is decided by WHICH catch-site caught the failure, never by message text:
|
|
|
14
14
|
host re-dispatches when the interpreter fingerprint changes.
|
|
15
15
|
- ``pyright_timeout`` -- this file's own per-query or file budget expired
|
|
16
16
|
while pyright stayed alive. Self-inflicted (``collateral=False``). A single
|
|
17
|
-
timeout does not restart: the process is slow, not dead.
|
|
17
|
+
timeout does not restart: the process is slow, not dead. In the calls pass
|
|
18
|
+
a per-query timeout with file budget remaining skips ONLY that function
|
|
19
|
+
(its sites disclosed as unresolved, the abandoned computation cancelled via
|
|
20
|
+
``$/cancelRequest``) and continues -- one pathological function no longer
|
|
21
|
+
forfeits the rest of the file (clarion-bf3986e301); the pass aborts, and
|
|
22
|
+
the facet degrades, only when the FILE budget itself is spent. A streak of
|
|
18
23
|
``MAX_CONSECUTIVE_TIMEOUT_FILES`` consecutive files whose calls pass timed
|
|
19
24
|
out is read as a wedged-but-alive process and restarted once, charged to
|
|
20
25
|
the run-level ``MAX_PYRIGHT_RESTARTS_PER_RUN`` budget; the streak's files
|
|
@@ -245,6 +250,15 @@ PYRIGHT_SHUTDOWN_TIMEOUT_SECS = 5.0
|
|
|
245
250
|
# handing a small one a budget it will never use.
|
|
246
251
|
PYRIGHT_FILE_TIMEOUT_BASE_SECS = 10.0
|
|
247
252
|
PYRIGHT_FILE_TIMEOUT_PER_FUNCTION_SECS = 0.25
|
|
253
|
+
# Second scaling axis (clarion-bf3986e301): pyright's evaluation work tracks
|
|
254
|
+
# the amount of CODE it must type-check, not the def count. elspeth's
|
|
255
|
+
# tool_batch.py (2,354 lines, 17 defs -- one a 1,199-line nested closure) got
|
|
256
|
+
# base + 17 * 0.25 ~= 14 s under the per-function-only budget: starved the
|
|
257
|
+
# same way the flat budget starved ML files (clarion-7f527d3d32). The budget
|
|
258
|
+
# takes ``max(per_function * n_functions, per_line * n_lines)`` -- for the
|
|
259
|
+
# common shape (many small defs) the per-function term still governs, and the
|
|
260
|
+
# cap (90 s) still bounds the monster files.
|
|
261
|
+
PYRIGHT_FILE_TIMEOUT_PER_LINE_SECS = 0.03
|
|
248
262
|
# The host watchdog (``DEFAULT_PLUGIN_FILE_TIMEOUT`` in
|
|
249
263
|
# crates/loomweave-cli/src/analyze.rs) kills an ``analyze_file`` call at 120s.
|
|
250
264
|
# Stay well under it so the plugin always hands back the partial evidence it
|
|
@@ -276,6 +290,21 @@ FILE_DEADLINE_TERMINAL_SAFETY_MARGIN_SECS = 15.0
|
|
|
276
290
|
# A respawn with less real headroom than this is pointless: pyright's
|
|
277
291
|
# initialize handshake rarely completes faster on a machine that just lost it.
|
|
278
292
|
MIN_RESPAWN_HEADROOM_SECS = 5.0
|
|
293
|
+
# The raising sites spell this label; the calls-pass skip logic keys on it to
|
|
294
|
+
# tell "the FILE's budget is spent" (abort the pass) from "one query's grant
|
|
295
|
+
# expired" (skip the function and continue, clarion-bf3986e301).
|
|
296
|
+
FILE_BUDGET_EXPIRED_METHOD = "analyze_file budget"
|
|
297
|
+
# Write bound for the fire-and-forget ``$/cancelRequest`` notify after an
|
|
298
|
+
# abandoned query: a healthy pyright drains it in microseconds; a full pipe
|
|
299
|
+
# means cancellation is hopeless anyway and the suppressed write timeout
|
|
300
|
+
# leaves the file budget to backstop.
|
|
301
|
+
CANCEL_WRITE_DEADLINE_SECS = 2.0
|
|
302
|
+
# Last stderr bytes attached to a FINDING_PYRIGHT_RESTART's metadata
|
|
303
|
+
# (clarion-bf3986e301 direction 3): enough to carry a crash reason (OOM kill,
|
|
304
|
+
# node stack trace tail) without bloating the findings channel. The in-memory
|
|
305
|
+
# tail keeps ``STDERR_TAIL_LIMIT`` for future probes; findings carry the tail
|
|
306
|
+
# end of it.
|
|
307
|
+
STDERR_TAIL_FINDING_LIMIT_BYTES = 512
|
|
279
308
|
|
|
280
309
|
|
|
281
310
|
def resolve_host_file_watchdog_secs(environ: Mapping[str, str] | None = None) -> float:
|
|
@@ -328,7 +357,7 @@ _TYPE_PARAMETER_NODE_NAMES = frozenset({"ParamSpec", "TypeVar", "TypeVarTuple"})
|
|
|
328
357
|
|
|
329
358
|
|
|
330
359
|
if TYPE_CHECKING:
|
|
331
|
-
from collections.abc import Callable, Mapping, Sequence
|
|
360
|
+
from collections.abc import Callable, Iterable, Mapping, Sequence
|
|
332
361
|
|
|
333
362
|
|
|
334
363
|
class LspTimeoutError(TimeoutError):
|
|
@@ -460,6 +489,7 @@ class PyrightSession:
|
|
|
460
489
|
call_timeout_secs: float = PYRIGHT_CALL_TIMEOUT_SECS,
|
|
461
490
|
file_timeout_base_secs: float = PYRIGHT_FILE_TIMEOUT_BASE_SECS,
|
|
462
491
|
file_timeout_per_function_secs: float = PYRIGHT_FILE_TIMEOUT_PER_FUNCTION_SECS,
|
|
492
|
+
file_timeout_per_line_secs: float = PYRIGHT_FILE_TIMEOUT_PER_LINE_SECS,
|
|
463
493
|
file_timeout_cap_secs: float = PYRIGHT_FILE_TIMEOUT_CAP_SECS,
|
|
464
494
|
max_restarts_per_run: int = MAX_PYRIGHT_RESTARTS_PER_RUN,
|
|
465
495
|
max_total_restarts_per_run: int = MAX_TOTAL_PYRIGHT_RESTARTS_PER_RUN,
|
|
@@ -496,6 +526,7 @@ class PyrightSession:
|
|
|
496
526
|
self.call_timeout_secs = call_timeout_secs
|
|
497
527
|
self.file_timeout_base_secs = file_timeout_base_secs
|
|
498
528
|
self.file_timeout_per_function_secs = file_timeout_per_function_secs
|
|
529
|
+
self.file_timeout_per_line_secs = file_timeout_per_line_secs
|
|
499
530
|
self.file_timeout_cap_secs = file_timeout_cap_secs
|
|
500
531
|
self.max_restarts_per_run = max_restarts_per_run
|
|
501
532
|
self.max_total_restarts_per_run = max_total_restarts_per_run
|
|
@@ -511,6 +542,12 @@ class PyrightSession:
|
|
|
511
542
|
self._stderr_thread: threading.Thread | None = None
|
|
512
543
|
self._stderr_tail = bytearray()
|
|
513
544
|
self._next_id = 1
|
|
545
|
+
# The id of the request currently awaiting its response, for
|
|
546
|
+
# ``$/cancelRequest`` after an abandoned per-query timeout
|
|
547
|
+
# (clarion-bf3986e301). Set by ``_request`` before the write, cleared
|
|
548
|
+
# on a completed round-trip; a timeout leaves it set for the skip
|
|
549
|
+
# path to consume.
|
|
550
|
+
self._request_id_in_flight: int | None = None
|
|
514
551
|
self._findings: list[Finding] = []
|
|
515
552
|
self._function_indexes: dict[Path, _FunctionIndex] = {}
|
|
516
553
|
self._index_parse_latency_ms: list[int] = []
|
|
@@ -637,15 +674,21 @@ class PyrightSession:
|
|
|
637
674
|
coverage=self._unavailable_coverage(),
|
|
638
675
|
)
|
|
639
676
|
|
|
640
|
-
deadline = self._deadline_for_file(
|
|
677
|
+
deadline = self._deadline_for_file(
|
|
678
|
+
path,
|
|
679
|
+
len(index.functions),
|
|
680
|
+
index.source.count("\n") + 1,
|
|
681
|
+
)
|
|
641
682
|
latency_started = time.perf_counter()
|
|
642
683
|
coverage = FacetCoverage()
|
|
643
684
|
try:
|
|
644
|
-
edges, unresolved, unresolved_sites, abort =
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
685
|
+
edges, unresolved, skipped_builtin, unresolved_sites, skipped_timeouts, abort = (
|
|
686
|
+
self._resolve_with_pyright(
|
|
687
|
+
path,
|
|
688
|
+
index,
|
|
689
|
+
requested,
|
|
690
|
+
deadline,
|
|
691
|
+
)
|
|
649
692
|
)
|
|
650
693
|
# These two arms remain reachable only from the ``didOpen`` notify
|
|
651
694
|
# that precedes the per-function loop: nothing was visited yet, so
|
|
@@ -659,6 +702,7 @@ class PyrightSession:
|
|
|
659
702
|
)
|
|
660
703
|
edges = []
|
|
661
704
|
unresolved = ast_call_sites_total
|
|
705
|
+
skipped_builtin = 0
|
|
662
706
|
unresolved_sites = []
|
|
663
707
|
coverage = FacetCoverage.degraded("pyright_timeout", transient=True)
|
|
664
708
|
except (LspTransportClosedError, BrokenPipeError, OSError) as exc:
|
|
@@ -667,9 +711,19 @@ class PyrightSession:
|
|
|
667
711
|
self._record_arrival_death(str(exc))
|
|
668
712
|
edges = []
|
|
669
713
|
unresolved = ast_call_sites_total
|
|
714
|
+
skipped_builtin = 0
|
|
670
715
|
unresolved_sites = []
|
|
671
716
|
coverage = FacetCoverage.degraded("pyright_restarting", transient=True, collateral=True)
|
|
672
717
|
else:
|
|
718
|
+
if abort is None and skipped_timeouts and skipped_timeouts == len(requested):
|
|
719
|
+
# EVERY function timed out: nothing was genuinely answered, so
|
|
720
|
+
# a ``complete`` claim would be a lie -- and it would reset the
|
|
721
|
+
# consecutive-timeout streak the ADR-057 wedge breaker needs to
|
|
722
|
+
# spot a wedged-but-alive server that survives per-function
|
|
723
|
+
# skip-and-continue. Partial timeouts (some functions resolved)
|
|
724
|
+
# stay complete with the skipped functions disclosed as
|
|
725
|
+
# unresolved sites + findings.
|
|
726
|
+
coverage = FacetCoverage.degraded("pyright_timeout", transient=True)
|
|
673
727
|
if abort is not None:
|
|
674
728
|
if abort.reason == "pyright_timeout":
|
|
675
729
|
self._record_finding(
|
|
@@ -692,6 +746,7 @@ class PyrightSession:
|
|
|
692
746
|
return CallResolutionResult(
|
|
693
747
|
edges=edges,
|
|
694
748
|
unresolved_call_sites_total=unresolved,
|
|
749
|
+
unresolved_call_sites_skipped_builtin_total=skipped_builtin,
|
|
695
750
|
unresolved_call_sites=unresolved_sites,
|
|
696
751
|
pyright_query_latency_ms=[latency_ms],
|
|
697
752
|
pyright_index_parse_latency_ms=self._pop_index_parse_latencies(),
|
|
@@ -814,7 +869,11 @@ class PyrightSession:
|
|
|
814
869
|
coverage=self._unavailable_coverage(),
|
|
815
870
|
)
|
|
816
871
|
|
|
817
|
-
deadline = self._deadline_for_file(
|
|
872
|
+
deadline = self._deadline_for_file(
|
|
873
|
+
path,
|
|
874
|
+
len(index.functions),
|
|
875
|
+
index.source.count("\n") + 1,
|
|
876
|
+
)
|
|
818
877
|
latency_started = time.perf_counter()
|
|
819
878
|
coverage = FacetCoverage()
|
|
820
879
|
self._reference_pass_timed_out = False
|
|
@@ -881,7 +940,7 @@ class PyrightSession:
|
|
|
881
940
|
index: _FunctionIndex,
|
|
882
941
|
functions: Sequence[_FunctionInfo],
|
|
883
942
|
deadline: float,
|
|
884
|
-
) -> tuple[list[CallsRawEdge], int, list[UnresolvedCallSite], _CallPassAbort | None]:
|
|
943
|
+
) -> tuple[list[CallsRawEdge], int, int, list[UnresolvedCallSite], int, _CallPassAbort | None]:
|
|
885
944
|
uri = path.as_uri()
|
|
886
945
|
self._notify(
|
|
887
946
|
"textDocument/didOpen",
|
|
@@ -898,9 +957,18 @@ class PyrightSession:
|
|
|
898
957
|
try:
|
|
899
958
|
edges: list[CallsRawEdge] = []
|
|
900
959
|
unresolved_total = 0
|
|
960
|
+
skipped_builtin_total = 0
|
|
901
961
|
unresolved_sites: list[UnresolvedCallSite] = []
|
|
902
962
|
abort: _CallPassAbort | None = None
|
|
963
|
+
skipped_function_timeouts = 0
|
|
903
964
|
remaining_start = len(functions)
|
|
965
|
+
# A bare call to an unshadowed builtin (``len(x)``, ``str(v)``,
|
|
966
|
+
# ``isinstance(...)``) can never resolve to a project entity, so
|
|
967
|
+
# it is neither an unresolved site worth persisting nor evidence
|
|
968
|
+
# of a resolution gap. Same shadowing-aware oracle the references
|
|
969
|
+
# pass uses; a star import disables it for the file
|
|
970
|
+
# (clarion-8a862d8f7e).
|
|
971
|
+
builtin_names = frozenset(_reference_fast_path_names(index.tree))
|
|
904
972
|
for position, function in enumerate(functions):
|
|
905
973
|
try:
|
|
906
974
|
function_edges, resolved_ranges = self._resolve_function_with_pyright(
|
|
@@ -910,20 +978,62 @@ class PyrightSession:
|
|
|
910
978
|
deadline,
|
|
911
979
|
)
|
|
912
980
|
except LspTimeoutError as exc:
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
981
|
+
if (
|
|
982
|
+
isinstance(exc, LspWriteTimeoutError)
|
|
983
|
+
or exc.method == FILE_BUDGET_EXPIRED_METHOD
|
|
984
|
+
or self._file_budget_expired(deadline)
|
|
985
|
+
):
|
|
986
|
+
# The FILE's window is spent (or the pipe itself timed
|
|
987
|
+
# out, where a "next function" would block the same
|
|
988
|
+
# way): abort with the evidence gathered so far.
|
|
989
|
+
abort = _CallPassAbort("pyright_timeout", exc.method, str(exc))
|
|
990
|
+
remaining_start = position
|
|
991
|
+
break
|
|
992
|
+
# One query's grant expired with file budget remaining:
|
|
993
|
+
# this function alone is unresolved. Cancel the abandoned
|
|
994
|
+
# computation so pyright can serve the next function, and
|
|
995
|
+
# continue the pass (clarion-bf3986e301) -- one
|
|
996
|
+
# pathological function no longer forfeits the file.
|
|
997
|
+
self._cancel_in_flight_request()
|
|
998
|
+
self._record_finding(
|
|
999
|
+
FINDING_PYRIGHT_CALL_RESOLUTION_TIMEOUT,
|
|
1000
|
+
f"pyright query timed out: {exc.method}; skipped "
|
|
1001
|
+
f"{function.entity_id} and continued the calls pass",
|
|
1002
|
+
method=exc.method,
|
|
1003
|
+
function=function.entity_id,
|
|
1004
|
+
skipped_function=True,
|
|
1005
|
+
)
|
|
1006
|
+
counted, skipped = _unresolved_call_site_total_for_function(
|
|
1007
|
+
function,
|
|
1008
|
+
set(),
|
|
1009
|
+
builtin_names,
|
|
1010
|
+
)
|
|
1011
|
+
unresolved_total += counted
|
|
1012
|
+
skipped_builtin_total += skipped
|
|
1013
|
+
unresolved_sites.extend(
|
|
1014
|
+
_unresolved_call_sites_for_function(index, function, set(), builtin_names),
|
|
1015
|
+
)
|
|
1016
|
+
skipped_function_timeouts += 1
|
|
1017
|
+
continue
|
|
916
1018
|
except (LspTransportClosedError, BrokenPipeError, OSError) as exc:
|
|
917
1019
|
abort = _CallPassAbort(self._in_flight_failure_reason(exc), None, str(exc))
|
|
918
1020
|
remaining_start = position
|
|
919
1021
|
break
|
|
920
1022
|
edges.extend(function_edges)
|
|
921
|
-
|
|
1023
|
+
counted, skipped = _unresolved_call_site_total_for_function(
|
|
922
1024
|
function,
|
|
923
1025
|
resolved_ranges,
|
|
1026
|
+
builtin_names,
|
|
924
1027
|
)
|
|
1028
|
+
unresolved_total += counted
|
|
1029
|
+
skipped_builtin_total += skipped
|
|
925
1030
|
unresolved_sites.extend(
|
|
926
|
-
_unresolved_call_sites_for_function(
|
|
1031
|
+
_unresolved_call_sites_for_function(
|
|
1032
|
+
index,
|
|
1033
|
+
function,
|
|
1034
|
+
resolved_ranges,
|
|
1035
|
+
builtin_names,
|
|
1036
|
+
),
|
|
927
1037
|
)
|
|
928
1038
|
if abort is not None:
|
|
929
1039
|
# INVARIANT: this fallback is only correct because a function is
|
|
@@ -938,11 +1048,24 @@ class PyrightSession:
|
|
|
938
1048
|
# its sites unresolved); track "functions with edges appended"
|
|
939
1049
|
# explicitly instead of relying on ``position`` if you add one.
|
|
940
1050
|
for function in functions[remaining_start:]:
|
|
941
|
-
|
|
1051
|
+
counted, skipped = _unresolved_call_site_total_for_function(
|
|
1052
|
+
function,
|
|
1053
|
+
set(),
|
|
1054
|
+
builtin_names,
|
|
1055
|
+
)
|
|
1056
|
+
unresolved_total += counted
|
|
1057
|
+
skipped_builtin_total += skipped
|
|
942
1058
|
unresolved_sites.extend(
|
|
943
|
-
_unresolved_call_sites_for_function(index, function, set()),
|
|
1059
|
+
_unresolved_call_sites_for_function(index, function, set(), builtin_names),
|
|
944
1060
|
)
|
|
945
|
-
return
|
|
1061
|
+
return (
|
|
1062
|
+
edges,
|
|
1063
|
+
unresolved_total,
|
|
1064
|
+
skipped_builtin_total,
|
|
1065
|
+
unresolved_sites,
|
|
1066
|
+
skipped_function_timeouts,
|
|
1067
|
+
abort,
|
|
1068
|
+
)
|
|
946
1069
|
finally:
|
|
947
1070
|
# The pipe may be the very thing that just failed: a notify over a
|
|
948
1071
|
# dead transport must not raise and discard the evidence above.
|
|
@@ -1024,7 +1147,7 @@ class PyrightSession:
|
|
|
1024
1147
|
edge["properties"] = {"candidates": candidate_ids}
|
|
1025
1148
|
function_edges.append(edge)
|
|
1026
1149
|
|
|
1027
|
-
return function_edges,
|
|
1150
|
+
return function_edges, _resolved_call_site_keys(function, grouped)
|
|
1028
1151
|
|
|
1029
1152
|
def _resolve_references_with_pyright(
|
|
1030
1153
|
self,
|
|
@@ -1184,11 +1307,11 @@ class PyrightSession:
|
|
|
1184
1307
|
"""The clock every deadline lives on (monotonic; overridable in tests)."""
|
|
1185
1308
|
return time.monotonic()
|
|
1186
1309
|
|
|
1187
|
-
def _deadline_for_file(self, path: Path, n_functions: int) -> float:
|
|
1310
|
+
def _deadline_for_file(self, path: Path, n_functions: int, n_lines: int) -> float:
|
|
1188
1311
|
existing = self._file_deadlines.get(path)
|
|
1189
1312
|
if existing is not None:
|
|
1190
1313
|
return existing
|
|
1191
|
-
deadline = self._now() + self._file_timeout_for(n_functions)
|
|
1314
|
+
deadline = self._now() + self._file_timeout_for(n_functions, n_lines)
|
|
1192
1315
|
anchor = self._file_started_at.get(path)
|
|
1193
1316
|
if anchor is not None:
|
|
1194
1317
|
# A spawn paid before this point (first file, or a restart) has
|
|
@@ -1214,15 +1337,17 @@ class PyrightSession:
|
|
|
1214
1337
|
return math.inf
|
|
1215
1338
|
return self._watchdog_ceiling_for(anchor) - self._now()
|
|
1216
1339
|
|
|
1217
|
-
def _file_timeout_for(self, n_functions: int) -> float:
|
|
1218
|
-
|
|
1219
|
-
|
|
1340
|
+
def _file_timeout_for(self, n_functions: int, n_lines: int) -> float:
|
|
1341
|
+
scaled = max(
|
|
1342
|
+
self.file_timeout_per_function_secs * n_functions,
|
|
1343
|
+
self.file_timeout_per_line_secs * n_lines,
|
|
1344
|
+
)
|
|
1345
|
+
return min(self.file_timeout_base_secs + scaled, self.file_timeout_cap_secs)
|
|
1220
1346
|
|
|
1221
1347
|
def _budgeted_timeout(self, deadline: float) -> float:
|
|
1222
1348
|
remaining = deadline - self._now()
|
|
1223
1349
|
if remaining <= 0:
|
|
1224
|
-
|
|
1225
|
-
raise LspTimeoutError(method)
|
|
1350
|
+
raise LspTimeoutError(FILE_BUDGET_EXPIRED_METHOD)
|
|
1226
1351
|
return min(self.call_timeout_secs, remaining)
|
|
1227
1352
|
|
|
1228
1353
|
def _write_deadline(self, deadline: float) -> float:
|
|
@@ -1242,8 +1367,7 @@ class PyrightSession:
|
|
|
1242
1367
|
|
|
1243
1368
|
def _ensure_file_budget(self, deadline: float) -> None:
|
|
1244
1369
|
if self._file_budget_expired(deadline):
|
|
1245
|
-
|
|
1246
|
-
raise LspTimeoutError(method)
|
|
1370
|
+
raise LspTimeoutError(FILE_BUDGET_EXPIRED_METHOD)
|
|
1247
1371
|
|
|
1248
1372
|
def _file_budget_expired(self, deadline: float) -> bool:
|
|
1249
1373
|
return deadline - self._now() <= 0
|
|
@@ -1384,6 +1508,7 @@ class PyrightSession:
|
|
|
1384
1508
|
file_attributed_restart_count=self._run_state.file_attributed_restart_count,
|
|
1385
1509
|
attribution="arrival",
|
|
1386
1510
|
reason=reason,
|
|
1511
|
+
stderr_tail=self._stderr_tail_snapshot(),
|
|
1387
1512
|
)
|
|
1388
1513
|
|
|
1389
1514
|
def _charge_run_level_restart(self, reason: str, *, attribution: str) -> bool:
|
|
@@ -1495,8 +1620,14 @@ class PyrightSession:
|
|
|
1495
1620
|
"""
|
|
1496
1621
|
state = self._run_state
|
|
1497
1622
|
state.file_attributed_restart_count += 1
|
|
1623
|
+
# Snapshot before any restart path terminates the process, which
|
|
1624
|
+
# clears the tail (clarion-bf3986e301: attribute the NEXT crash class
|
|
1625
|
+
# without a manual probe).
|
|
1626
|
+
stderr_tail = self._stderr_tail_snapshot()
|
|
1498
1627
|
if self._total_restart_budget_exceeded():
|
|
1499
|
-
self._record_in_flight_restart_finding(
|
|
1628
|
+
self._record_in_flight_restart_finding(
|
|
1629
|
+
reason, outcome="cap_exceeded", stderr_tail=stderr_tail
|
|
1630
|
+
)
|
|
1500
1631
|
self._trip_total_restart_cap(reason)
|
|
1501
1632
|
return
|
|
1502
1633
|
outcome = self._restart_process_for_file(path)
|
|
@@ -1505,9 +1636,11 @@ class PyrightSession:
|
|
|
1505
1636
|
# spawn path already triaged it -- a transient deferral (next file
|
|
1506
1637
|
# retries) or a permanent disable with its OWN ``disabled_reason``.
|
|
1507
1638
|
state.file_attributed_respawn_failure_count += 1
|
|
1508
|
-
self._record_in_flight_restart_finding(reason, outcome=outcome)
|
|
1639
|
+
self._record_in_flight_restart_finding(reason, outcome=outcome, stderr_tail=stderr_tail)
|
|
1509
1640
|
|
|
1510
|
-
def _record_in_flight_restart_finding(
|
|
1641
|
+
def _record_in_flight_restart_finding(
|
|
1642
|
+
self, reason: str, *, outcome: str, stderr_tail: str
|
|
1643
|
+
) -> None:
|
|
1511
1644
|
self._record_finding(
|
|
1512
1645
|
FINDING_PYRIGHT_RESTART,
|
|
1513
1646
|
f"pyright subprocess died during this file's request; {outcome}",
|
|
@@ -1516,6 +1649,7 @@ class PyrightSession:
|
|
|
1516
1649
|
attribution="in_flight",
|
|
1517
1650
|
outcome=outcome,
|
|
1518
1651
|
reason=reason,
|
|
1652
|
+
stderr_tail=stderr_tail,
|
|
1519
1653
|
)
|
|
1520
1654
|
|
|
1521
1655
|
def _restart_process_for_file(self, path: Path) -> _RestartOutcome:
|
|
@@ -1563,9 +1697,20 @@ class PyrightSession:
|
|
|
1563
1697
|
process = self._process
|
|
1564
1698
|
self._process = None
|
|
1565
1699
|
if process is not None and process.poll() is None:
|
|
1700
|
+
# The venv's ``pyright-langserver`` is a Python wrapper around a
|
|
1701
|
+
# ``node`` grandchild; killing the wrapper alone leaves node
|
|
1702
|
+
# behind, reparented to pid 1 and -- when wedged, which is why we
|
|
1703
|
+
# are killing it -- spinning forever (clarion-ebf404dfbb). Walk
|
|
1704
|
+
# the descendants BEFORE the kill (they are reparented the moment
|
|
1705
|
+
# the wrapper dies), then kill wrapper and descendants.
|
|
1706
|
+
pid = getattr(process, "pid", None)
|
|
1707
|
+
descendants = _descendant_pids(pid) if isinstance(pid, int) else []
|
|
1566
1708
|
process.kill()
|
|
1567
1709
|
with contextlib.suppress(subprocess.TimeoutExpired):
|
|
1568
1710
|
process.wait(timeout=2)
|
|
1711
|
+
for pid in descendants:
|
|
1712
|
+
with contextlib.suppress(ProcessLookupError, PermissionError):
|
|
1713
|
+
os.kill(pid, signal.SIGKILL)
|
|
1569
1714
|
if self._stderr_thread is not None:
|
|
1570
1715
|
self._stderr_thread.join(timeout=2)
|
|
1571
1716
|
self._stderr_thread = None
|
|
@@ -1818,6 +1963,7 @@ class PyrightSession:
|
|
|
1818
1963
|
# One deadline for the whole request, write included: the write is the
|
|
1819
1964
|
# half that blocked on elspeth (clarion-e3ab8a4131).
|
|
1820
1965
|
deadline = self._now() + timeout_secs
|
|
1966
|
+
self._request_id_in_flight = request_id
|
|
1821
1967
|
self._write_message(
|
|
1822
1968
|
{
|
|
1823
1969
|
"jsonrpc": "2.0",
|
|
@@ -1843,6 +1989,7 @@ class PyrightSession:
|
|
|
1843
1989
|
continue
|
|
1844
1990
|
if response.get("id") != request_id:
|
|
1845
1991
|
continue
|
|
1992
|
+
self._request_id_in_flight = None
|
|
1846
1993
|
if "error" in response:
|
|
1847
1994
|
raise LspTransportClosedError(str(response["error"]))
|
|
1848
1995
|
process.poll()
|
|
@@ -2001,8 +2148,13 @@ class PyrightSession:
|
|
|
2001
2148
|
if index.parse_status == "syntax_error":
|
|
2002
2149
|
return None
|
|
2003
2150
|
key = _range_start_key(raw_selection)
|
|
2004
|
-
|
|
2005
|
-
|
|
2151
|
+
# Functions AND classes: pyright reports ``Name(...)`` naming a class
|
|
2152
|
+
# as an outgoing call whose target item IS the class (selection range
|
|
2153
|
+
# on the class name) -- never its ``__init__`` -- so an instantiation
|
|
2154
|
+
# is a ``calls`` edge to the class entity (clarion-e5224c3aff). The
|
|
2155
|
+
# function-only index dropped every such target as unresolved.
|
|
2156
|
+
if key is not None and key in index.entity_by_name_position:
|
|
2157
|
+
return index.entity_by_name_position[key]
|
|
2006
2158
|
return _containing_function_id(index, raw_selection)
|
|
2007
2159
|
|
|
2008
2160
|
def _is_internal_project_path(self, path: Path) -> bool:
|
|
@@ -2022,6 +2174,42 @@ class PyrightSession:
|
|
|
2022
2174
|
self._index_parse_latency_ms.append(index.parse_latency_ms)
|
|
2023
2175
|
return index
|
|
2024
2176
|
|
|
2177
|
+
def _cancel_in_flight_request(self) -> None:
|
|
2178
|
+
"""Fire-and-forget ``$/cancelRequest`` for an abandoned query.
|
|
2179
|
+
|
|
2180
|
+
pyright honours LSP cancellation between evaluation steps, so telling
|
|
2181
|
+
it the caller stopped waiting lets it drop the pathological
|
|
2182
|
+
computation and answer the NEXT function's query instead of chewing
|
|
2183
|
+
through the rest of the file's budget behind our back
|
|
2184
|
+
(clarion-bf3986e301). Best-effort by design: a transport already
|
|
2185
|
+
broken, or a pipe already full, means the file budget backstops --
|
|
2186
|
+
every failure here is suppressed. (A partial-write timeout still
|
|
2187
|
+
invalidates the frame inside ``_write_message``, so a corrupted
|
|
2188
|
+
stream is put down rather than limped on.)
|
|
2189
|
+
"""
|
|
2190
|
+
request_id = self._request_id_in_flight
|
|
2191
|
+
self._request_id_in_flight = None
|
|
2192
|
+
if request_id is None:
|
|
2193
|
+
return
|
|
2194
|
+
with contextlib.suppress(
|
|
2195
|
+
LspTimeoutError, LspTransportClosedError, BrokenPipeError, OSError
|
|
2196
|
+
):
|
|
2197
|
+
self._notify(
|
|
2198
|
+
"$/cancelRequest",
|
|
2199
|
+
{"id": request_id},
|
|
2200
|
+
deadline=self._now() + CANCEL_WRITE_DEADLINE_SECS,
|
|
2201
|
+
)
|
|
2202
|
+
|
|
2203
|
+
def _stderr_tail_snapshot(self) -> str:
|
|
2204
|
+
"""The last stderr bytes of the CURRENT process, finding-sized.
|
|
2205
|
+
|
|
2206
|
+
Callers must snapshot BEFORE ``_terminate_process``, which clears the
|
|
2207
|
+
tail so two processes' stderr never mix in one finding.
|
|
2208
|
+
"""
|
|
2209
|
+
return bytes(self._stderr_tail[-STDERR_TAIL_FINDING_LIMIT_BYTES:]).decode(
|
|
2210
|
+
"utf-8", "replace"
|
|
2211
|
+
)
|
|
2212
|
+
|
|
2025
2213
|
def _record_finding(self, subcode: str, message: str, **metadata: object) -> None:
|
|
2026
2214
|
self._findings.append(
|
|
2027
2215
|
{
|
|
@@ -2375,6 +2563,38 @@ def _reference_accumulator_to_edge(
|
|
|
2375
2563
|
return edge
|
|
2376
2564
|
|
|
2377
2565
|
|
|
2566
|
+
def _descendant_pids(pid: int) -> list[int]:
|
|
2567
|
+
"""Every live descendant of ``pid`` via ``/proc/<pid>/task/*/children``.
|
|
2568
|
+
|
|
2569
|
+
Linux only (``CONFIG_PROC_CHILDREN``, on by default); elsewhere, or once
|
|
2570
|
+
the process is gone, the walk yields nothing and only the direct child
|
|
2571
|
+
is killed -- the pre-existing behaviour.
|
|
2572
|
+
"""
|
|
2573
|
+
found: list[int] = []
|
|
2574
|
+
frontier = [pid]
|
|
2575
|
+
while frontier:
|
|
2576
|
+
parent = frontier.pop()
|
|
2577
|
+
task_dir = Path("/proc") / str(parent) / "task"
|
|
2578
|
+
try:
|
|
2579
|
+
tasks = list(task_dir.iterdir())
|
|
2580
|
+
except OSError:
|
|
2581
|
+
continue
|
|
2582
|
+
for task in tasks:
|
|
2583
|
+
try:
|
|
2584
|
+
listed = (task / "children").read_text(encoding="ascii").split()
|
|
2585
|
+
except OSError:
|
|
2586
|
+
continue
|
|
2587
|
+
for raw in listed:
|
|
2588
|
+
try:
|
|
2589
|
+
child = int(raw)
|
|
2590
|
+
except ValueError:
|
|
2591
|
+
continue
|
|
2592
|
+
if child not in found:
|
|
2593
|
+
found.append(child)
|
|
2594
|
+
frontier.append(child)
|
|
2595
|
+
return found
|
|
2596
|
+
|
|
2597
|
+
|
|
2378
2598
|
def _function_call_sites(
|
|
2379
2599
|
node: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
2380
2600
|
source_lines: Sequence[str],
|
|
@@ -2385,27 +2605,88 @@ def _function_call_sites(
|
|
|
2385
2605
|
return visitor.call_sites
|
|
2386
2606
|
|
|
2387
2607
|
|
|
2608
|
+
def _is_builtin_call_site(call_site: _CallSite, builtin_names: frozenset[str]) -> bool:
|
|
2609
|
+
"""A bare ``Name(...)`` call whose name is an unshadowed builtin in this file.
|
|
2610
|
+
|
|
2611
|
+
Attribute calls (``pytest.raises``) are deliberately NOT matched: they may
|
|
2612
|
+
resolve to an external module (counted by the external skip) or, under a
|
|
2613
|
+
rebinding, to a project entity -- only pyright can tell.
|
|
2614
|
+
"""
|
|
2615
|
+
return call_site.callee_expr in builtin_names
|
|
2616
|
+
|
|
2617
|
+
|
|
2618
|
+
def _resolved_call_site_keys(
|
|
2619
|
+
function: _FunctionInfo,
|
|
2620
|
+
resolved_ranges: Iterable[tuple[int, int, int, int]],
|
|
2621
|
+
) -> set[tuple[int, int, int, int]]:
|
|
2622
|
+
"""The AST call-site keys the resolved ranges account for.
|
|
2623
|
+
|
|
2624
|
+
pyright anchors an outgoing call's ``fromRange`` on the callee's terminal
|
|
2625
|
+
token (``helper`` in ``self.helper()``), while ``_CallSite`` spans the
|
|
2626
|
+
whole callee expression (``self.helper``). Matching the two by equality
|
|
2627
|
+
only ever succeeded for bare ``Name()`` calls, so every resolved attribute
|
|
2628
|
+
call was also reported as an unresolved site -- breaking the partition
|
|
2629
|
+
(resolved + unresolved + skipped builtins == AST call sites) the host and
|
|
2630
|
+
``entity_callers_list``'s ``traversal_complete`` depend on. A range
|
|
2631
|
+
resolves the SMALLEST call site containing it: in ``Svc().helper()`` the
|
|
2632
|
+
``Svc`` token claims the inner site and ``helper`` the outer one.
|
|
2633
|
+
"""
|
|
2634
|
+
sites_by_line: dict[int, list[_CallSite]] = {}
|
|
2635
|
+
for site in function.call_sites:
|
|
2636
|
+
for line in range(site.line, site.end_line + 1):
|
|
2637
|
+
sites_by_line.setdefault(line, []).append(site)
|
|
2638
|
+
resolved: set[tuple[int, int, int, int]] = set()
|
|
2639
|
+
for start_line, start_character, end_line, end_character in resolved_ranges:
|
|
2640
|
+
best: _CallSite | None = None
|
|
2641
|
+
for site in sites_by_line.get(start_line, ()):
|
|
2642
|
+
if (site.line, site.character) > (start_line, start_character):
|
|
2643
|
+
continue
|
|
2644
|
+
if (end_line, end_character) > (site.end_line, site.end_character):
|
|
2645
|
+
continue
|
|
2646
|
+
if best is None or (site.end_line - site.line, site.end_character - site.character) < (
|
|
2647
|
+
best.end_line - best.line,
|
|
2648
|
+
best.end_character - best.character,
|
|
2649
|
+
):
|
|
2650
|
+
best = site
|
|
2651
|
+
if best is not None:
|
|
2652
|
+
resolved.add((best.line, best.character, best.end_line, best.end_character))
|
|
2653
|
+
return resolved
|
|
2654
|
+
|
|
2655
|
+
|
|
2388
2656
|
def _unresolved_call_site_total_for_function(
|
|
2389
2657
|
function: _FunctionInfo,
|
|
2390
2658
|
resolved_ranges: set[tuple[int, int, int, int]],
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2659
|
+
builtin_names: frozenset[str] = frozenset(),
|
|
2660
|
+
) -> tuple[int, int]:
|
|
2661
|
+
"""``(unresolved, skipped_builtin)`` for one function's call sites.
|
|
2662
|
+
|
|
2663
|
+
The two are disjoint and, with the resolved sites, partition the AST call
|
|
2664
|
+
sites; the host treats the unresolved-site LIST as authoritative only when
|
|
2665
|
+
its length equals ``unresolved``, so a builtin skip must leave both.
|
|
2666
|
+
"""
|
|
2667
|
+
unresolved = 0
|
|
2668
|
+
skipped_builtin = 0
|
|
2669
|
+
for call_site in function.call_sites:
|
|
2670
|
+
range_key = (
|
|
2396
2671
|
call_site.line,
|
|
2397
2672
|
call_site.character,
|
|
2398
2673
|
call_site.end_line,
|
|
2399
2674
|
call_site.end_character,
|
|
2400
2675
|
)
|
|
2401
|
-
|
|
2402
|
-
|
|
2676
|
+
if range_key in resolved_ranges:
|
|
2677
|
+
continue
|
|
2678
|
+
if _is_builtin_call_site(call_site, builtin_names):
|
|
2679
|
+
skipped_builtin += 1
|
|
2680
|
+
continue
|
|
2681
|
+
unresolved += 1
|
|
2682
|
+
return unresolved, skipped_builtin
|
|
2403
2683
|
|
|
2404
2684
|
|
|
2405
2685
|
def _unresolved_call_sites_for_function(
|
|
2406
2686
|
index: _FunctionIndex,
|
|
2407
2687
|
function: _FunctionInfo,
|
|
2408
2688
|
resolved_ranges: set[tuple[int, int, int, int]],
|
|
2689
|
+
builtin_names: frozenset[str] = frozenset(),
|
|
2409
2690
|
) -> list[UnresolvedCallSite]:
|
|
2410
2691
|
unresolved: list[UnresolvedCallSite] = []
|
|
2411
2692
|
for site_ordinal, call_site in enumerate(function.call_sites):
|
|
@@ -2417,6 +2698,8 @@ def _unresolved_call_sites_for_function(
|
|
|
2417
2698
|
)
|
|
2418
2699
|
if range_key in resolved_ranges:
|
|
2419
2700
|
continue
|
|
2701
|
+
if _is_builtin_call_site(call_site, builtin_names):
|
|
2702
|
+
continue
|
|
2420
2703
|
if len(call_site.callee_expr.encode("utf-8")) > MAX_UNRESOLVED_CALLEE_EXPR_BYTES:
|
|
2421
2704
|
continue
|
|
2422
2705
|
start_byte = _position_to_byte(index, call_site.line, call_site.character)
|
|
@@ -35,7 +35,7 @@ from loomweave_plugin_python.pyright_session import PyrightRunState, PyrightSess
|
|
|
35
35
|
from loomweave_plugin_python.stdout_guard import install_stdio
|
|
36
36
|
from loomweave_plugin_python.wardline_descriptor import WardlineVocabulary, load_wardline_descriptor
|
|
37
37
|
|
|
38
|
-
ONTOLOGY_VERSION = "0.
|
|
38
|
+
ONTOLOGY_VERSION = "0.13.0"
|
|
39
39
|
|
|
40
40
|
# Plugin-side Content-Length sanity cap. Matches the host's ADR-021 §2b
|
|
41
41
|
# default (8 MiB) so the plugin never emits a frame the host would kill us
|
|
@@ -212,6 +212,7 @@ def handle_analyze_file(params: dict[str, Any], state: ServerState) -> dict[str,
|
|
|
212
212
|
"""Read the requested file, extract entities + edges, return AnalyzeFileResult shape."""
|
|
213
213
|
empty_stats = {
|
|
214
214
|
"unresolved_call_sites_total": 0,
|
|
215
|
+
"unresolved_call_sites_skipped_builtin_total": 0,
|
|
215
216
|
"unresolved_call_sites": [],
|
|
216
217
|
"reference_sites_total": 0,
|
|
217
218
|
"references_resolved_total": 0,
|
|
@@ -258,6 +259,9 @@ def handle_analyze_file(params: dict[str, Any], state: ServerState) -> dict[str,
|
|
|
258
259
|
state.pyright_files_since_restart = 0
|
|
259
260
|
stats = {
|
|
260
261
|
"unresolved_call_sites_total": result.stats.unresolved_call_sites_total,
|
|
262
|
+
"unresolved_call_sites_skipped_builtin_total": (
|
|
263
|
+
result.stats.unresolved_call_sites_skipped_builtin_total
|
|
264
|
+
),
|
|
261
265
|
"unresolved_call_sites": result.stats.unresolved_call_sites,
|
|
262
266
|
"reference_sites_total": result.stats.reference_sites_total,
|
|
263
267
|
"references_resolved_total": result.stats.references_resolved_total,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[plugin]
|
|
2
2
|
name = "loomweave-plugin-python"
|
|
3
3
|
plugin_id = "python"
|
|
4
|
-
version = "1.
|
|
4
|
+
version = "1.6.0"
|
|
5
5
|
protocol_version = "1.0"
|
|
6
6
|
# Bare basename per ADR-021 §Layer 1 + WP2 scrub commit eb0a41d — the host
|
|
7
7
|
# refuses manifests whose `executable` carries any path component.
|
|
@@ -57,13 +57,17 @@ rule_id_prefix = "LMWV-PY-"
|
|
|
57
57
|
# runner-wrapper, `sys.argv`, and argparse `cli-command` tagging; 0.12.0
|
|
58
58
|
# declares the complete static classifier set so supported-empty results are
|
|
59
59
|
# distinguishable from unsupported classifiers and forces unchanged files to
|
|
60
|
-
# re-dispatch. Classifier declarations do not create entity-tag rows.
|
|
60
|
+
# re-dispatch. Classifier declarations do not create entity-tag rows. 0.13.0
|
|
61
|
+
# widens `calls` targets to class entities: an instantiation ``Name(...)``
|
|
62
|
+
# is a call to the class (clarion-e5224c3aff) -- a semantic widening of an
|
|
63
|
+
# existing kind, bumped so unchanged files re-dispatch and pick up the edges.
|
|
64
|
+
# NOTE:
|
|
61
65
|
# ADR-007's summary-cache key is the 5-tuple (entity_id, content_hash,
|
|
62
66
|
# prompt_template_id, model_tier, guidance_fingerprint) — ontology_version is
|
|
63
67
|
# handshake-validation, NOT a cache-key component. New edge rows miss the
|
|
64
68
|
# cache by component-1 of the 5-tuple organically (no edges live in the
|
|
65
69
|
# 5-tuple yet anyway).
|
|
66
|
-
ontology_version = "0.
|
|
70
|
+
ontology_version = "0.13.0"
|
|
67
71
|
classifier_tags = [
|
|
68
72
|
"cli-command",
|
|
69
73
|
"data-model",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
loomweave_plugin_python/__init__.py,sha256=5mhialMRwU5-O9sz_IX8_mGzsMX1RYvQJAwawhAXlr0,95
|
|
2
|
+
loomweave_plugin_python/__main__.py,sha256=oy1S1Ru7WdX6jcAVhsqNI25LxAbQq3jHBlETBt20fyM,376
|
|
3
|
+
loomweave_plugin_python/call_resolver.py,sha256=sc4b-h1CLcaPF1wu7CpQ5nDh75O7xaZokqe_PRS8bcE,5130
|
|
4
|
+
loomweave_plugin_python/entity_id.py,sha256=l7_5yyh6WVH9xIzrQVCDb2GmBMhovOVmuQezcZIVf8k,2691
|
|
5
|
+
loomweave_plugin_python/extractor.py,sha256=hGjyLvYGJF4XbyvAfwxnkfQQB6h4U0--uT-ndUwvbGE,102488
|
|
6
|
+
loomweave_plugin_python/interpreter.py,sha256=mWrxnNN6eMQUR0TUT4uxA5bzL48lyqR7KHgtEF7T8os,4320
|
|
7
|
+
loomweave_plugin_python/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
loomweave_plugin_python/pyright_session.py,sha256=Yf-5W8qWX1hC3kv1mjshrxOS0MGC1sKKivJG1r-gMK0,139286
|
|
9
|
+
loomweave_plugin_python/qualname.py,sha256=gk6N2e4tMvufnPgpVZLv--c9_9gW5y9YvJIwUArffFg,2102
|
|
10
|
+
loomweave_plugin_python/reference_resolver.py,sha256=yp9Ff59A130mEn5txCh7jpVcpjQjy1EmXFoUhdyA4as,2830
|
|
11
|
+
loomweave_plugin_python/server.py,sha256=wiTi98XaFa3LKo_OnGtRWpwM4UuOYXHePS6wwzu3FlM,14079
|
|
12
|
+
loomweave_plugin_python/stdout_guard.py,sha256=tUzcXyvXuy3-dXB81_xCNk8KWE3mqtsWSge81KPDhKg,2007
|
|
13
|
+
loomweave_plugin_python/wardline_descriptor.py,sha256=4TX7PCZLyYVI3dd4xT98ueNEcYpUd8OV0oyOJO0wseE,16354
|
|
14
|
+
loomweave_plugin_python-1.6.0.data/data/share/loomweave/plugins/python/plugin.toml,sha256=O0onhlYaXi3mdt7XBsuAnsXg2xYOSEEkpzFgreyZ4FE,5218
|
|
15
|
+
loomweave_plugin_python-1.6.0.dist-info/METADATA,sha256=CyALXx3Vp0cWUG6jlBff_rXB1F7x8l9LJMwgX5JrPpw,4885
|
|
16
|
+
loomweave_plugin_python-1.6.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
17
|
+
loomweave_plugin_python-1.6.0.dist-info/entry_points.txt,sha256=X1DQOYU5o1z2lBOJoh9JgQR5pg9fd_Yd8KozTHdfZqA,82
|
|
18
|
+
loomweave_plugin_python-1.6.0.dist-info/RECORD,,
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
loomweave_plugin_python/__init__.py,sha256=kMw1zndW_xa7Jd0dYOYPhgeFazYz1eeXt5CsZkl_-Us,95
|
|
2
|
-
loomweave_plugin_python/__main__.py,sha256=oy1S1Ru7WdX6jcAVhsqNI25LxAbQq3jHBlETBt20fyM,376
|
|
3
|
-
loomweave_plugin_python/call_resolver.py,sha256=JorafhgCDP8LlHJFcBiWbeU8_2HdBLtwsfgdDHZtUiM,4931
|
|
4
|
-
loomweave_plugin_python/entity_id.py,sha256=l7_5yyh6WVH9xIzrQVCDb2GmBMhovOVmuQezcZIVf8k,2691
|
|
5
|
-
loomweave_plugin_python/extractor.py,sha256=7sYCohPMvYV39WTO8gZW5iDqIEGqvoOVgHijbR148ng,102292
|
|
6
|
-
loomweave_plugin_python/interpreter.py,sha256=mWrxnNN6eMQUR0TUT4uxA5bzL48lyqR7KHgtEF7T8os,4320
|
|
7
|
-
loomweave_plugin_python/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
loomweave_plugin_python/pyright_session.py,sha256=VdtdHdzAQGGKf0x1p8kBhea_sdv4ISUqMHgNiEX0RKg,125134
|
|
9
|
-
loomweave_plugin_python/qualname.py,sha256=gk6N2e4tMvufnPgpVZLv--c9_9gW5y9YvJIwUArffFg,2102
|
|
10
|
-
loomweave_plugin_python/reference_resolver.py,sha256=yp9Ff59A130mEn5txCh7jpVcpjQjy1EmXFoUhdyA4as,2830
|
|
11
|
-
loomweave_plugin_python/server.py,sha256=WBaiEQwrhw8iA8m5FyOuJQ_ydRLJJ1GIwa64DOFHQ1w,13884
|
|
12
|
-
loomweave_plugin_python/stdout_guard.py,sha256=tUzcXyvXuy3-dXB81_xCNk8KWE3mqtsWSge81KPDhKg,2007
|
|
13
|
-
loomweave_plugin_python/wardline_descriptor.py,sha256=4TX7PCZLyYVI3dd4xT98ueNEcYpUd8OV0oyOJO0wseE,16354
|
|
14
|
-
loomweave_plugin_python-1.5.1.data/data/share/loomweave/plugins/python/plugin.toml,sha256=Zh4v74rc03Ha9BGIqJx2NuxP5ZM8tyGnWD6Kf_rCtF4,4981
|
|
15
|
-
loomweave_plugin_python-1.5.1.dist-info/METADATA,sha256=kNzUfBMP5OgHwHSycUd6JIctPbRwLb2bNCxW8fCqtUg,4885
|
|
16
|
-
loomweave_plugin_python-1.5.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
17
|
-
loomweave_plugin_python-1.5.1.dist-info/entry_points.txt,sha256=X1DQOYU5o1z2lBOJoh9JgQR5pg9fd_Yd8KozTHdfZqA,82
|
|
18
|
-
loomweave_plugin_python-1.5.1.dist-info/RECORD,,
|
|
File without changes
|
{loomweave_plugin_python-1.5.1.dist-info → loomweave_plugin_python-1.6.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|