sourcecode 2.6.3__py3-none-any.whl → 2.6.5__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.

Potentially problematic release.


This version of sourcecode might be problematic. Click here for more details.

sourcecode/__init__.py CHANGED
@@ -4,4 +4,4 @@ ASK Engine is the product. ``ask`` is the canonical CLI command; ``sourcecode``
4
4
  the legacy compatibility alias and the Python/PyPI package name. See
5
5
  docs/PRODUCT_IDENTITY.md (normative)."""
6
6
 
7
- __version__ = "2.6.3"
7
+ __version__ = "2.6.5"
sourcecode/cli.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import hashlib
4
4
  import json
5
+ import difflib
5
6
  import os
6
7
  import sys
7
8
  import threading
@@ -331,6 +332,54 @@ def _reject_path_before_subcommand(path_token: str, subcommand: str) -> "NoRetur
331
332
  raise SystemExit(2)
332
333
 
333
334
 
335
+ def _reject_unknown_command(token: str) -> "NoReturn":
336
+ """Refuse a first positional that is neither a known subcommand nor a real path.
337
+
338
+ `ask` accepts a bare repository path (`ask ./repo`) as an implicit scan, so a
339
+ mistyped subcommand used to be swallowed as a path and then surfaced a
340
+ misleading error naming the *wrong* token — `ask spring-audi` complained the
341
+ directory 'spring-audi' did not exist, and `ask notacommand /tmp` complained
342
+ 'No such command /tmp' (the path, not the typo). Name the token the user
343
+ actually got wrong, and suggest the closest real commands.
344
+ """
345
+ matches = difflib.get_close_matches(token, sorted(_SUBCOMMANDS), n=3, cutoff=0.6)
346
+ first = f"error: '{token}' is not a known command"
347
+ if matches:
348
+ first += f". Did you mean: {', '.join(matches)}?"
349
+ else:
350
+ first += " and is not an existing directory to scan."
351
+ print(
352
+ first + "\n run 'ask --help' to list commands, or pass an existing "
353
+ "repository path.",
354
+ file=sys.stderr,
355
+ )
356
+ raise SystemExit(2)
357
+
358
+
359
+ def _has_trailing_nonsubcommand_positional(args: list[str], after: int) -> bool:
360
+ """True when a bare positional that is NOT a subcommand appears after index
361
+ ``after`` (options and the values they consume are skipped).
362
+
363
+ The implicit scan (`ask <path>`) owns exactly one positional, and a valid
364
+ invocation with two positionals always starts with a subcommand. So a first
365
+ non-subcommand positional followed by another non-subcommand positional —
366
+ `ask notacommand /tmp` — is malformed: the user meant a command. A trailing
367
+ token that IS a subcommand is left for ``_reject_path_before_subcommand``."""
368
+ skip_next = False
369
+ for arg in args[after + 1:]:
370
+ if skip_next:
371
+ skip_next = False
372
+ continue
373
+ if arg.startswith("-"):
374
+ if arg.split("=")[0] in _OPTIONS_WITH_VALUE and "=" not in arg:
375
+ skip_next = True
376
+ continue
377
+ if arg in _SUBCOMMANDS:
378
+ return False # wrong-order path/subcommand — handled elsewhere
379
+ return True
380
+ return False
381
+
382
+
334
383
  def _preprocess_args(args: list[str]) -> list[str]:
335
384
  """Extract a repository path token from an args list and store it in _detected_path.
336
385
 
@@ -363,7 +412,13 @@ def _preprocess_args(args: list[str]) -> list[str]:
363
412
  return result # known subcommand — leave for Click to dispatch
364
413
  if _path_index >= 0:
365
414
  continue # a later positional is the subcommand's own business
366
- # First genuine positional: treat as repository path
415
+ # First genuine positional. `ask` treats it as the repository path to scan
416
+ # (existence is validated downstream, not here). But if it is followed by
417
+ # another non-subcommand positional, the one-path scan cannot own both —
418
+ # `ask notacommand /tmp` — so the user meant a command: name the token they
419
+ # got wrong instead of swallowing it and erroring on the next one.
420
+ if _has_trailing_nonsubcommand_positional(result, i):
421
+ _reject_unknown_command(arg)
367
422
  _set_detected_path(arg)
368
423
  _path_index = i
369
424
  if _path_index >= 0:
@@ -1278,11 +1333,23 @@ def main(
1278
1333
  _raw_path_input = _get_detected_path()
1279
1334
  target = Path(_raw_path_input).resolve()
1280
1335
  if not target.exists():
1336
+ # A non-existent path that closely matches a command name is almost always a
1337
+ # mistyped subcommand (`ask spring-audi`) swallowed as an implicit-scan path,
1338
+ # not a real directory. Point at the likely command instead of only the path.
1339
+ _cmd_matches = difflib.get_close_matches(
1340
+ _raw_path_input, sorted(_SUBCOMMANDS), n=3, cutoff=0.6
1341
+ )
1342
+ _hint = "Pass an existing repository directory."
1343
+ if _cmd_matches:
1344
+ _hint = (
1345
+ f"If you meant a command, try: {', '.join('ask ' + m for m in _cmd_matches)}. "
1346
+ "Otherwise pass an existing repository directory."
1347
+ )
1281
1348
  _emit_error_json(
1282
1349
  INVALID_INPUT_CODE,
1283
1350
  f"Directory '{_raw_path_input}' does not exist.",
1284
1351
  path=_raw_path_input,
1285
- hint="Pass an existing repository directory.",
1352
+ hint=_hint,
1286
1353
  expected="An existing directory path.",
1287
1354
  )
1288
1355
  raise typer.Exit(code=1)
@@ -5528,7 +5595,7 @@ def spring_audit_cmd(
5528
5595
  help="Accepted for compatibility; this command always reads fresh source (no snapshot cache). No-op.",
5529
5596
  ),
5530
5597
  ) -> None:
5531
- """Spring semantic audit: TX anomalies (TX-001..005) + security surface (SEC-001..003).
5598
+ """Spring semantic audit: TX anomalies (TX-001..006) + security surface (SEC-001..003).
5532
5599
 
5533
5600
  \b
5534
5601
  Detects:
@@ -5537,6 +5604,7 @@ def spring_audit_cmd(
5537
5604
  TX-003 readOnly=true boundary propagating to write operation
5538
5605
  TX-004 NOT_SUPPORTED/NEVER within active TX chain
5539
5606
  TX-005 Exception swallowing inside @Transactional
5607
+ TX-006 Self-invocation of @Transactional sibling (proxy bypass)
5540
5608
  SEC-001 Unsecured endpoint in annotation_based security model
5541
5609
  SEC-002 CVE-2025-41248: @PreAuthorize on inherited method from generic supertype
5542
5610
  SEC-003 @Transactional on @Controller/@RestController (TX in wrong layer)
@@ -2098,6 +2098,21 @@ def _extract_body_facts(
2098
2098
  receiver, callee = m.group(1), m.group(2)
2099
2099
  if callee in _CALL_KEYWORDS:
2100
2100
  continue
2101
+ if receiver is None:
2102
+ # A chained/qualified call — `expr().foo()`, `getBean().foo()`,
2103
+ # `a.b().foo()` — leaves the receiver group empty because the
2104
+ # receiver is not a bare identifier. It must NOT be mistaken for an
2105
+ # unqualified self-call: if the callee is immediately preceded by '.'
2106
+ # the call is qualified (goes through whatever the preceding
2107
+ # expression returns, e.g. a Spring proxy), so mark it as a
2108
+ # non-self expression receiver. (openmrs OrderServiceImpl routes
2109
+ # REQUIRES_NEW through Context.getOrderService()....() precisely to
2110
+ # keep the proxy — that is correct code, not a self-invocation.)
2111
+ j = m.start() - 1
2112
+ while j >= 0 and body[j] in " \t\r\n":
2113
+ j -= 1
2114
+ if j >= 0 and body[j] == ".":
2115
+ receiver = "<expr>"
2101
2116
  resolved: Optional[str] = None
2102
2117
  if receiver in (None, "this") and callee in sib:
2103
2118
  cands = [f for f in sib[callee] if f != caller.symbol]
@@ -1036,7 +1036,7 @@ class ImpactOrchestrator:
1036
1036
  )
1037
1037
  else:
1038
1038
  warnings.append(
1039
- f"Self-referential exclusion (BUG #2): {self_excluded} member(s) of the "
1039
+ f"Self-referential exclusion: {self_excluded} member(s) of the "
1040
1040
  f"analyzed class were dropped from callers — a class's own methods are "
1041
1041
  f"members, not external callers (they do not count toward blast radius)."
1042
1042
  )
@@ -6,9 +6,8 @@ Patterns implemented (Phase 2):
6
6
  TX-003 readOnly=true boundary propagating into write-capable callee
7
7
  TX-004 NOT_SUPPORTED or NEVER invoked from transactional call chain
8
8
  TX-005 Exception swallowing inside @Transactional method (regex)
9
-
10
- Self-invocation via this.method() intentionally excluded from Phase 2:
11
- requires AST-level analysis, regex produces too many false positives.
9
+ TX-006 Self-invocation of a @Transactional sibling — proxy bypass (Semantic-IR
10
+ invocation atoms; unambiguous same-class calls only)
12
11
 
13
12
  All patterns are deterministic and never raise.
14
13
  """
@@ -683,6 +682,142 @@ class _TX005ExceptionSwallowing:
683
682
  return blocks
684
683
 
685
684
 
685
+ # ---------------------------------------------------------------------------
686
+ # TX-006: self-invocation of a @Transactional sibling — proxy bypass
687
+ # ---------------------------------------------------------------------------
688
+
689
+ class _TX006SelfInvocation:
690
+ """Same-instance call to a @Transactional sibling method bypasses the proxy.
691
+
692
+ Spring TX advice is applied by a proxy that wraps the bean. A call that does
693
+ NOT go through that proxy — an unqualified `foo()` or an explicit `this.foo()`
694
+ to a method of the SAME class — reaches the target directly, so its
695
+ method-level @Transactional is silently ignored at runtime (Sonar S2229).
696
+
697
+ Detection is structural and VAI-clean, reading the Semantic-IR invocation
698
+ atoms already produced by build_canonical_ir (cir.body_facts):
699
+
700
+ - the atom's receiver is None (unqualified) or "this" → not via a proxy
701
+ - resolved_fqn is set → unambiguous sibling
702
+ (the IR only resolves a sibling when receiver∈{None,"this"} and exactly one
703
+ same-class method matches — overloaded/ambiguous targets stay None → skipped)
704
+ - the resolved sibling carries a METHOD-LEVEL @Transactional → advice would apply
705
+
706
+ Class-level-only @Transactional is deliberately NOT flagged: the enclosing
707
+ proxy already applies it to the outer entry method, so the self-call runs in
708
+ that same transaction — no advice is lost. The self-injection workaround
709
+ (`self.foo()` where `self` is an injected same-type field) uses a named field
710
+ receiver, never None/"this", so it never resolves as a sibling → no false
711
+ positive on the legitimate escape hatch.
712
+ """
713
+ pattern_id = "TX-006"
714
+ severity = "high" # nominal (Protocol); real severity is tiered per finding
715
+
716
+ # Propagations whose whole purpose is a distinct transaction boundary: a bypass
717
+ # demonstrably breaks them (a new/suspended/forbidden TX that never happens, or a
718
+ # runtime IllegalTransactionStateException). Everything else — a plain REQUIRED/
719
+ # SUPPORTS callee, or a readOnly getter (bypass loses only the read-only
720
+ # optimization, not correctness) — degrades to running in the caller's TX: a real
721
+ # bypass with lower blast radius → medium, matching TX-002/003 discipline.
722
+ _HIGH_IMPACT_PROPAGATION = frozenset({
723
+ "REQUIRES_NEW", "NESTED", "NOT_SUPPORTED", "NEVER", "MANDATORY",
724
+ })
725
+
726
+ def analyze(
727
+ self,
728
+ cir: "CanonicalRepositoryIR",
729
+ tx_index: TransactionBoundaryIndex,
730
+ root: Optional[Path],
731
+ *,
732
+ model: Optional[SpringSemanticModel] = None,
733
+ ) -> list[SpringFinding]:
734
+ findings: list[SpringFinding] = []
735
+ body_facts = getattr(cir, "body_facts", {}) or {}
736
+ _seen_ids: set[str] = set()
737
+
738
+ for caller_fqn, atoms in body_facts.items():
739
+ if not isinstance(atoms, list):
740
+ continue
741
+ for atom in atoms:
742
+ if not isinstance(atom, dict) or atom.get("fact") != "invocation":
743
+ continue
744
+ # Only same-instance calls bypass the proxy: unqualified or `this.`.
745
+ if atom.get("receiver") not in (None, "this"):
746
+ continue
747
+ target = atom.get("resolved_fqn")
748
+ if not target:
749
+ continue # ambiguous/overloaded/unresolved sibling — honest skip
750
+ # The bypass only matters when the sibling declares its OWN
751
+ # method-level @Transactional; class-level advice already covers
752
+ # the outer entry method and is not lost by the self-call.
753
+ tb = tx_index.by_symbol.get(target)
754
+ if tb is None or tb.scope != "method":
755
+ continue
756
+
757
+ _fid = SpringFinding.make_id(self.pattern_id, f"{caller_fqn}→{target}")
758
+ if _fid in _seen_ids:
759
+ continue
760
+ _seen_ids.add(_fid)
761
+
762
+ caller_simple = caller_fqn.rsplit(".", 1)[-1].replace("#", ".")
763
+ callee_simple = target.rsplit(".", 1)[-1].replace("#", ".")
764
+
765
+ # Tier severity by what the bypass demonstrably breaks. A propagation
766
+ # that mandates a distinct boundary (or forbids an active TX) is
767
+ # high; a plain REQUIRED/SUPPORTS/readOnly callee degrades to the
768
+ # caller's TX — still a real bypass, lower blast radius → medium.
769
+ if tb.propagation in self._HIGH_IMPACT_PROPAGATION:
770
+ sev = "high"
771
+ else:
772
+ sev = "medium"
773
+
774
+ findings.append(SpringFinding(
775
+ id=_fid,
776
+ pattern_id=self.pattern_id,
777
+ category="tx",
778
+ severity=sev,
779
+ confidence="high",
780
+ title="Self-invocation of @Transactional method — Spring proxy bypass",
781
+ symbol=target,
782
+ source_file=tb.source_file,
783
+ evidence={
784
+ "caller": caller_fqn,
785
+ "callee": target,
786
+ "receiver": atom.get("receiver") or "(unqualified)",
787
+ "annotation": "@Transactional",
788
+ "callee_propagation": tb.propagation,
789
+ "callee_read_only": tb.read_only,
790
+ "proxy_mechanism": "proxy self-invocation",
791
+ "reason": (
792
+ "same-instance call does not pass through the Spring proxy"
793
+ ),
794
+ },
795
+ explanation=(
796
+ f"{caller_simple} calls sibling {callee_simple} on the same "
797
+ "instance (unqualified / this.). The call does not pass through "
798
+ f"the Spring proxy, so the @Transactional on {callee_simple} "
799
+ f"(propagation={tb.propagation}"
800
+ f"{', readOnly=true' if tb.read_only else ''}) is silently "
801
+ "ignored — it executes in the caller's transaction context "
802
+ "(or none at all), not the one its annotation declares. "
803
+ "No new/independent transaction, propagation, or rollback boundary is applied."
804
+ ),
805
+ fix_hint=(
806
+ "Move the @Transactional method to a separate Spring bean and "
807
+ "call it through the injected dependency, or inject the bean into "
808
+ "itself and call via the injected proxy reference, or use "
809
+ "TransactionTemplate for programmatic transaction control."
810
+ ),
811
+ limitations=[
812
+ "Only unambiguous sibling calls are analyzed — overloaded targets "
813
+ "are reported as unresolved and skipped",
814
+ ],
815
+ related_symbols=[target.split("#")[0]],
816
+ ))
817
+
818
+ return findings
819
+
820
+
686
821
  # ---------------------------------------------------------------------------
687
822
  # Default pattern registry
688
823
  # ---------------------------------------------------------------------------
@@ -693,6 +828,7 @@ _DEFAULT_TX_PATTERNS: list[TxPattern] = [
693
828
  _TX003ReadOnlyWritePropagation(), # type: ignore[list-item]
694
829
  _TX004TxSuspensionRisk(), # type: ignore[list-item]
695
830
  _TX005ExceptionSwallowing(), # type: ignore[list-item]
831
+ _TX006SelfInvocation(), # type: ignore[list-item]
696
832
  ]
697
833
 
698
834
 
@@ -777,7 +913,8 @@ def run_tx_audit(
777
913
  _spring_detected = tx_index.stats()["total"] > 0 or model.bean_graph.has_spring_beans()
778
914
 
779
915
  _tx_limitations = [
780
- "Self-invocation via this.method() not detected requires AST-level analysis",
916
+ "Self-invocation (this.method()) detected for unambiguous same-class calls "
917
+ "(TX-006); overloaded targets are reported as unresolved and skipped",
781
918
  "Dynamic dispatch (interface/polymorphic calls) may produce incomplete call chains",
782
919
  ]
783
920
  for _err in getattr(engine, "_last_analysis_errors", []):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 2.6.3
3
+ Version: 2.6.5
4
4
  Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
5
5
  License-File: LICENSE
6
6
  Keywords: agents,ai,codebase,context,developer-tools,llm
@@ -1,4 +1,4 @@
1
- sourcecode/__init__.py,sha256=inJYWLATw0hWFueMieZTLMVFkMV-j2YjHCR5sz3Dlh0,308
1
+ sourcecode/__init__.py,sha256=P9Q5ycRTxeFZymRbTTRJpWf5WptRYL1ZBeI0mayU4iI,308
2
2
  sourcecode/adaptive_scanner.py,sha256=yJBKjNpkY6bpueYJ2YnRezen3sYZDecEt7WaaNWdqug,9466
3
3
  sourcecode/archetype.py,sha256=UrmiONHfqLl7UfGzjECoQBO28hcM_1rvzn0hCt0EtjU,35033
4
4
  sourcecode/architectural_baseline.py,sha256=7QzJri4pbL3nzAn9gNutZW6mZR8HHD6H2C2H1EEwYPE,17904
@@ -13,7 +13,7 @@ sourcecode/canonical_ir.py,sha256=LdP_Ri3rl0M5p-MY0ypVvQwhq1WuUF1FmaHid7zyzEg,29
13
13
  sourcecode/change_plan.py,sha256=MNgNyu4zrLGvBBaXPwyCh-T2ZGaUQX3Hm4W87uuhZ3w,7750
14
14
  sourcecode/cir_graphs.py,sha256=9G0HHj1kw2325IDyzo2OpX73BNswEckecf4MZUXB4JM,12078
15
15
  sourcecode/classifier.py,sha256=JBzPwSSrDG-tUHAbcKB678HRbjLpD-ohzbzzO62mgpo,20114
16
- sourcecode/cli.py,sha256=vlr2fQDesPFaSGa6qLhG6GP7xWU6F-u0U_qjc03r14M,362994
16
+ sourcecode/cli.py,sha256=UZzhlXAHXT0saZcnhr0QQ2LXUIkP2qfB-V_hlf9tDJ4,366305
17
17
  sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
18
18
  sourcecode/compare.py,sha256=jdePg0dNCxFpysiTGMsROL5XABLjy_zVUgVeySvdb0o,7514
19
19
  sourcecode/confidence_analyzer.py,sha256=vnbPI-20FnHdjO6STxHW8fbaxmB4A7y58io63ibFZjc,21586
@@ -65,7 +65,7 @@ sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
65
65
  sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
66
66
  sourcecode/rename_refactor.py,sha256=h6dNFlB9aZ_3q6heeHBkgXQeXaT03nvPSsYH6P8qxFg,12965
67
67
  sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
68
- sourcecode/repository_ir.py,sha256=wD6ODf8nNijDIh01FWyPoY1jplpPLWvfKl_5rAvLVMQ,322680
68
+ sourcecode/repository_ir.py,sha256=HilsZiTt-WkEWuKPWJDB9vtR3WELTYsB2FsNXdEUuEE,323648
69
69
  sourcecode/ris.py,sha256=Hw8TakTQ6hku-Abf2k8954NwkrH_sP73_8wVH8x5khc,22079
70
70
  sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
71
71
  sourcecode/scanner.py,sha256=z3CV0rcGunu0Y8mpNgp07wI7nxT0pxw1BkXRRtI0Rpo,9609
@@ -79,11 +79,11 @@ sourcecode/semantic_services.py,sha256=nbUuPv-F01USTt_9CHT8iy_ucCIw3fz4W3Aquea_p
79
79
  sourcecode/serializer.py,sha256=zmN3pOcfxaQdxCE-jKoKudFjof9Sc_q7jpcBzNIA0EY,129493
80
80
  sourcecode/spring_event_topology.py,sha256=5_ON_21Le5zbG-1GRc5GLIi5HJfy_QjcXLVPC5WeUGQ,18055
81
81
  sourcecode/spring_findings.py,sha256=EX7kLZLN74CFyR9iZPm3CI115BfFKNd4WRPuErNljZM,5729
82
- sourcecode/spring_impact.py,sha256=ATOa7jOg7kL78vW0uZiGBvtB-ph9bCiEGH0kRtABHxg,73108
82
+ sourcecode/spring_impact.py,sha256=09t29l4bKhA-F6GXwC7kAzWfsWBOs095OmDNasEy1Og,73099
83
83
  sourcecode/spring_model.py,sha256=zOAgFmrRbG4a6KLm1TJl55aWMyPNsz3OS3FSczqPG6A,16594
84
84
  sourcecode/spring_security_audit.py,sha256=Rk-aSohezdc7YDYbSoJquVnwpkDB8ty1BCD-4Hc4R5A,22832
85
85
  sourcecode/spring_semantic.py,sha256=jteQ1PkY9ArFJv0embg_jBIdbOxqrk9mQ2Xz8OF_FKA,14214
86
- sourcecode/spring_tx_analyzer.py,sha256=7qXwQR9QbpVxbxJC-mawBMXM9xzydtZeq5L18cP_6GE,33884
86
+ sourcecode/spring_tx_analyzer.py,sha256=lp0h5Pzzd3fPxHAAagMmG8PqR1-v2uVpFIG1o7tCWQs,41148
87
87
  sourcecode/summarizer.py,sha256=sr0-tfecFKCr-fSkPPWbl-t9HC7SY2ZxkjnnXX8DB2A,26621
88
88
  sourcecode/tree_utils.py,sha256=8GAkIfQAsvtEudIeW1l4ooH_oRtrWR8cpJQJsEa_Pfw,2093
89
89
  sourcecode/type_usage_surface.py,sha256=51IrKRQoIoRnlsiDjHnqpJBn2rc6E59aRhgS0HTzAF0,4428
@@ -148,8 +148,8 @@ sourcecode/telemetry/consent.py,sha256=LIAO9ohJZF8OuZwM4u1VWtALlYfTCCKq4wV3Vwc7i
148
148
  sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
149
149
  sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
150
150
  sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
151
- sourcecode-2.6.3.dist-info/METADATA,sha256=83aYwUnc4szlY7QK1pyjw5suiM3HMkRKeRznWyPzd20,10851
152
- sourcecode-2.6.3.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
153
- sourcecode-2.6.3.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
154
- sourcecode-2.6.3.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
155
- sourcecode-2.6.3.dist-info/RECORD,,
151
+ sourcecode-2.6.5.dist-info/METADATA,sha256=Sxx6ytWQ8YgF4odFcMnh42AC-_r5zj1mqkoSFb9Cmyo,10851
152
+ sourcecode-2.6.5.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
153
+ sourcecode-2.6.5.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
154
+ sourcecode-2.6.5.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
155
+ sourcecode-2.6.5.dist-info/RECORD,,