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

@@ -0,0 +1,304 @@
1
+ """verify-edit Fase V5 — custom domain rules (the wide, defensive moat).
2
+
3
+ The generic axes (contract / @Transactional / security / bean-wiring) encode Spring
4
+ runtime semantics that a competitor could, with effort, replicate. This module lets a
5
+ *client* declare invariants of THEIR codebase and have ``verify-edit`` enforce them —
6
+ "no RestController touches a JPA DAO directly", "every /payroll endpoint requires role
7
+ X". Those rules live in the client's repo and no external tool knows them; that is what
8
+ makes the moat inreplicable.
9
+
10
+ Discipline (roadmap §7):
11
+
12
+ * **VAI (Vendor-Agnostic Inference).** Our evaluator NEVER branches on a proprietary
13
+ name. A selector reasons purely by *structure* — has-annotation, fqn-matches-regex,
14
+ symbol-kind, modifier, source-file-matches. The concrete names ("RestController",
15
+ "DaoJpa", "/payroll") arrive as config **data**; they are the client's evidence,
16
+ never a hardcoded ``if fqn == "com.acme.Foo"`` in this code.
17
+ * **Determinism.** ``evaluate`` is a pure function of the CIR. Violation ids are stable
18
+ so the verdict layer can diff HEAD vs working and report only what the edit *added*.
19
+ * **Zero-config over the existing graph.** No rules file ⇒ no rules ⇒ nothing blocks.
20
+ Rules run against the CIR that is already built; no new extraction.
21
+
22
+ Rule file (repo-root ``.ask/verify-rules.json``, fallback ``.sourcecode/verify-rules.json``):
23
+
24
+ {
25
+ "rules": [
26
+ {
27
+ "id": "no-controller-to-dao",
28
+ "description": "REST controllers must not touch a JPA DAO directly",
29
+ "kind": "forbidden_edge",
30
+ "edge_type": "injects",
31
+ "from": {"annotation": "@RestController"},
32
+ "to": {"fqn": ".*DaoJpa$"}
33
+ },
34
+ {
35
+ "id": "payroll-needs-role",
36
+ "description": "every /payroll endpoint requires the PAYROLL role",
37
+ "kind": "require_endpoint_security",
38
+ "path": "^/payroll",
39
+ "require": {"role": "PAYROLL"}
40
+ }
41
+ ]
42
+ }
43
+ """
44
+ from __future__ import annotations
45
+
46
+ import json
47
+ import re
48
+ from dataclasses import dataclass
49
+ from pathlib import Path
50
+ from typing import Any, Optional
51
+
52
+ RULES_SCHEMA = "verify-rules-v1"
53
+ _RULE_FILES = (".ask/verify-rules.json", ".sourcecode/verify-rules.json")
54
+ _EDGE_KIND = "forbidden_edge"
55
+ _ENDPOINT_KIND = "require_endpoint_security"
56
+ _KINDS = (_EDGE_KIND, _ENDPOINT_KIND)
57
+
58
+
59
+ class RulesError(ValueError):
60
+ """The rules file exists but is malformed — verify-edit reports *unverified*, never
61
+ a silent pass (a broken ruleset must not read as "no violations")."""
62
+
63
+
64
+ # ── selector: a pure structural predicate over one graph node ────────────────────
65
+ @dataclass(frozen=True)
66
+ class Selector:
67
+ """Matches a graph node by structure only. Every present clause must hold (AND).
68
+ An empty selector matches every node. Names are data, never branched on by kind."""
69
+
70
+ annotation: Optional[str] = None # node.annotations contains this (exact, e.g. "@RestController")
71
+ fqn: Optional[str] = None # regex, fully matched against node fqn
72
+ symbol_kind: Optional[str] = None # exact node symbol_kind (class|interface|method|field)
73
+ modifier: Optional[str] = None # node.modifiers contains this (e.g. "public")
74
+ source_file: Optional[str] = None # regex, searched against node source_file
75
+ _fqn_re: Any = None
76
+ _sf_re: Any = None
77
+
78
+ @classmethod
79
+ def from_dict(cls, d: Any, *, where: str) -> "Selector":
80
+ if d is None:
81
+ return cls()
82
+ if not isinstance(d, dict):
83
+ raise RulesError(f"{where}: selector must be an object, got {type(d).__name__}")
84
+ known = {"annotation", "fqn", "symbol_kind", "modifier", "source_file"}
85
+ unknown = set(d) - known
86
+ if unknown:
87
+ raise RulesError(f"{where}: unknown selector field(s) {sorted(unknown)}")
88
+ try:
89
+ fqn_re = re.compile(d["fqn"]) if d.get("fqn") else None
90
+ sf_re = re.compile(d["source_file"]) if d.get("source_file") else None
91
+ except re.error as exc:
92
+ raise RulesError(f"{where}: invalid regex ({exc})") from exc
93
+ return cls(
94
+ annotation=d.get("annotation"),
95
+ fqn=d.get("fqn"),
96
+ symbol_kind=d.get("symbol_kind"),
97
+ modifier=d.get("modifier"),
98
+ source_file=d.get("source_file"),
99
+ _fqn_re=fqn_re,
100
+ _sf_re=sf_re,
101
+ )
102
+
103
+ def is_empty(self) -> bool:
104
+ return not any((self.annotation, self.fqn, self.symbol_kind, self.modifier, self.source_file))
105
+
106
+ def matches(self, node: dict) -> bool:
107
+ if self.annotation is not None and self.annotation not in (node.get("annotations") or []):
108
+ return False
109
+ if self._fqn_re is not None and not self._fqn_re.fullmatch(node.get("fqn") or ""):
110
+ return False
111
+ if self.symbol_kind is not None and (node.get("symbol_kind") or "") != self.symbol_kind:
112
+ return False
113
+ if self.modifier is not None and self.modifier not in (node.get("modifiers") or []):
114
+ return False
115
+ if self._sf_re is not None and not self._sf_re.search(node.get("source_file") or ""):
116
+ return False
117
+ return True
118
+
119
+
120
+ @dataclass(frozen=True)
121
+ class Violation:
122
+ rule_id: str
123
+ subject: str # the offending symbol / endpoint id
124
+ detail: str
125
+
126
+ @property
127
+ def id(self) -> str:
128
+ """Stable across runs, so the verdict layer can diff HEAD vs working."""
129
+ return f"{self.rule_id}::{self.subject}"
130
+
131
+ def to_dict(self) -> dict:
132
+ return {"rule_id": self.rule_id, "subject": self.subject, "detail": self.detail}
133
+
134
+
135
+ # ── rules ────────────────────────────────────────────────────────────────────────
136
+ @dataclass(frozen=True)
137
+ class ForbiddenEdgeRule:
138
+ """No edge of ``edge_type`` may go from a node matching ``from_sel`` to a node
139
+ matching ``to_sel``. Reasons over the CIR edge list — structure, not names."""
140
+
141
+ id: str
142
+ description: str
143
+ edge_type: str
144
+ from_sel: Selector
145
+ to_sel: Selector
146
+
147
+ def evaluate(self, cir: Any) -> list[Violation]:
148
+ nodes = {n.get("fqn"): n for n in _nodes(cir) if n.get("fqn")}
149
+ owner = _owner_type_map(cir) # member fqn → declaring type fqn
150
+ out: list[Violation] = []
151
+ for e in cir.call_graph:
152
+ if e.get("type") != self.edge_type:
153
+ continue
154
+ src, dst = e.get("from"), e.get("to")
155
+ fn, tn = nodes.get(src), nodes.get(dst)
156
+ if fn is None or tn is None:
157
+ continue # an endpoint of the edge is outside the graph — cannot judge
158
+ if self._endpoint_matches(self.from_sel, fn, nodes, owner) and \
159
+ self._endpoint_matches(self.to_sel, tn, nodes, owner):
160
+ out.append(Violation(
161
+ self.id, f"{src} -{self.edge_type}-> {dst}",
162
+ f"forbidden {self.edge_type} edge {src} → {dst}",
163
+ ))
164
+ return out
165
+
166
+ @staticmethod
167
+ def _endpoint_matches(sel: Selector, node: dict, nodes: dict, owner: dict) -> bool:
168
+ """Selector matches an edge endpoint iff it matches that node OR the type that
169
+ declares it. Edges are recorded at member granularity (a field's ``injects``, a
170
+ method's ``calls``) but client invariants are usually stated at type granularity
171
+ ("no RestController touches a DAO"), so the annotation/kind on the owning class
172
+ must count. Purely structural — the owner is resolved via ``contained_in``."""
173
+ if sel.matches(node):
174
+ return True
175
+ owner_fqn = owner.get(node.get("fqn"))
176
+ owner_node = nodes.get(owner_fqn) if owner_fqn else None
177
+ return owner_node is not None and sel.matches(owner_node)
178
+
179
+
180
+ @dataclass(frozen=True)
181
+ class RequireEndpointSecurityRule:
182
+ """Every endpoint whose path matches ``path_re`` must carry the required policy
183
+ and/or role. A missing/weaker policy on a matched endpoint is a violation."""
184
+
185
+ id: str
186
+ description: str
187
+ path_re: Any
188
+ require_policy: Optional[str]
189
+ require_role: Optional[str]
190
+
191
+ def evaluate(self, cir: Any) -> list[Violation]:
192
+ out: list[Violation] = []
193
+ for ep in cir.endpoints:
194
+ if not self.path_re.search(ep.path or ""):
195
+ continue
196
+ sec = ep.security
197
+ missing: list[str] = []
198
+ if self.require_policy is not None and (sec is None or sec.policy != self.require_policy):
199
+ got = repr(sec.policy) if sec else "None"
200
+ missing.append(f"policy={self.require_policy!r} (got {got})")
201
+ if self.require_role is not None and (sec is None or self.require_role not in (sec.effective_roles or [])):
202
+ missing.append(f"role={self.require_role!r}")
203
+ if missing:
204
+ out.append(Violation(
205
+ self.id, ep.id,
206
+ f"endpoint {ep.method} {ep.path} missing required {', '.join(missing)}",
207
+ ))
208
+ return out
209
+
210
+
211
+ Rule = Any # ForbiddenEdgeRule | RequireEndpointSecurityRule
212
+
213
+
214
+ def _nodes(cir: Any) -> list[dict]:
215
+ return cir._raw_ir.get("graph", {}).get("nodes", []) or []
216
+
217
+
218
+ def _owner_type_map(cir: Any) -> dict[str, str]:
219
+ """member fqn → declaring type fqn, from ``contained_in`` edges (field/method → class).
220
+ Lets a type-level selector match an edge endpoint recorded at member granularity."""
221
+ return {e["from"]: e["to"] for e in cir.call_graph
222
+ if e.get("type") == "contained_in" and e.get("from") and e.get("to")}
223
+
224
+
225
+ # ── loading / parsing ────────────────────────────────────────────────────────────
226
+ def _rule_from_dict(d: Any, idx: int) -> Rule:
227
+ where = f"rule[{idx}]"
228
+ if not isinstance(d, dict):
229
+ raise RulesError(f"{where}: must be an object")
230
+ rid = d.get("id")
231
+ if not rid or not isinstance(rid, str):
232
+ raise RulesError(f"{where}: missing string 'id'")
233
+ kind = d.get("kind")
234
+ if kind not in _KINDS:
235
+ raise RulesError(f"{where} ({rid}): 'kind' must be one of {list(_KINDS)}, got {kind!r}")
236
+ desc = d.get("description") or ""
237
+
238
+ if kind == _EDGE_KIND:
239
+ edge_type = d.get("edge_type")
240
+ if not edge_type or not isinstance(edge_type, str):
241
+ raise RulesError(f"{where} ({rid}): forbidden_edge needs a string 'edge_type'")
242
+ from_sel = Selector.from_dict(d.get("from"), where=f"{where} ({rid}).from")
243
+ to_sel = Selector.from_dict(d.get("to"), where=f"{where} ({rid}).to")
244
+ if from_sel.is_empty() and to_sel.is_empty():
245
+ raise RulesError(f"{where} ({rid}): 'from' and 'to' cannot both be empty (would ban all edges)")
246
+ return ForbiddenEdgeRule(rid, desc, edge_type, from_sel, to_sel)
247
+
248
+ # require_endpoint_security
249
+ path = d.get("path")
250
+ if not path or not isinstance(path, str):
251
+ raise RulesError(f"{where} ({rid}): require_endpoint_security needs a string 'path' regex")
252
+ try:
253
+ path_re = re.compile(path)
254
+ except re.error as exc:
255
+ raise RulesError(f"{where} ({rid}): invalid 'path' regex ({exc})") from exc
256
+ req = d.get("require")
257
+ if not isinstance(req, dict) or not (req.get("policy") or req.get("role")):
258
+ raise RulesError(f"{where} ({rid}): 'require' must set at least one of 'policy' / 'role'")
259
+ return RequireEndpointSecurityRule(rid, desc, path_re, req.get("policy"), req.get("role"))
260
+
261
+
262
+ def parse_rules(raw: Any) -> list[Rule]:
263
+ """Parse an already-decoded rules document into rule objects. Raises RulesError
264
+ on any malformation (duplicate ids included) so the axis degrades to *unverified*."""
265
+ if not isinstance(raw, dict) or "rules" not in raw:
266
+ raise RulesError("rules file must be an object with a 'rules' array")
267
+ rules_raw = raw["rules"]
268
+ if not isinstance(rules_raw, list):
269
+ raise RulesError("'rules' must be an array")
270
+ rules = [_rule_from_dict(d, i) for i, d in enumerate(rules_raw)]
271
+ seen: set[str] = set()
272
+ for r in rules:
273
+ if r.id in seen:
274
+ raise RulesError(f"duplicate rule id {r.id!r}")
275
+ seen.add(r.id)
276
+ return rules
277
+
278
+
279
+ def rules_path(root: Path) -> Optional[Path]:
280
+ for rel in _RULE_FILES:
281
+ p = root / rel
282
+ if p.is_file():
283
+ return p
284
+ return None
285
+
286
+
287
+ def load_rules(root: Path) -> list[Rule]:
288
+ """Load the repo's custom rules. Absent file ⇒ ``[]`` (zero-config, nothing blocks).
289
+ Present-but-broken ⇒ RulesError (caller degrades to unverified — never a silent pass)."""
290
+ p = rules_path(Path(root))
291
+ if p is None:
292
+ return []
293
+ try:
294
+ raw = json.loads(p.read_text(encoding="utf-8"))
295
+ except (OSError, json.JSONDecodeError) as exc:
296
+ raise RulesError(f"could not read {p}: {exc}") from exc
297
+ return parse_rules(raw)
298
+
299
+
300
+ def evaluate_all(rules: list[Rule], cir: Any) -> list[Violation]:
301
+ out: list[Violation] = []
302
+ for r in rules:
303
+ out.extend(r.evaluate(cir))
304
+ return out
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 2.6.2
3
+ Version: 2.6.3
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=Xl4vKr6-4l-yWpoN7FzgqDyXd54m2LAMPUtDWX2CdD0,308
1
+ sourcecode/__init__.py,sha256=inJYWLATw0hWFueMieZTLMVFkMV-j2YjHCR5sz3Dlh0,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=kivJiIF7Rw-Eal-3_m2CG5E9i66UUuOLLyquLgUM0N0,359485
16
+ sourcecode/cli.py,sha256=vlr2fQDesPFaSGa6qLhG6GP7xWU6F-u0U_qjc03r14M,362994
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
@@ -52,6 +52,7 @@ sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7
52
52
  sourcecode/migrate_check.py,sha256=hsKNwipl-bSqRsYqG1O_CLbXpMlqtnN-Bif3hpHpnjg,108676
53
53
  sourcecode/openapi_surface.py,sha256=BTt0K-woZbkbWTN77IkqeBm_Okag9owR0848fmot8sk,16207
54
54
  sourcecode/output_budget.py,sha256=Js9yUlfQtPhqBl9R6wn_9UHVjjJc3GtLcqyfjf5t50Q,9869
55
+ sourcecode/parse_cache.py,sha256=EB2Eb23S3Z2UlyUIPetXv-Tu-1niB6eWVThmraLCyyA,5505
55
56
  sourcecode/path_filters.py,sha256=qPKO7kRmVp2y9zjLNSuAVCbcpZrIInHE4QulLUlzPFI,10412
56
57
  sourcecode/perf.py,sha256=GAcEoouPIlPMCQIcHNToxK6K3WdIR-lj9aFg4prOYJI,9743
57
58
  sourcecode/pr_comment_renderer.py,sha256=KmcjMruhR44gjzMDJwjBSkWP9QEvh8xWBLyxzxoRbj0,14542
@@ -64,7 +65,7 @@ sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
64
65
  sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
65
66
  sourcecode/rename_refactor.py,sha256=h6dNFlB9aZ_3q6heeHBkgXQeXaT03nvPSsYH6P8qxFg,12965
66
67
  sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
67
- sourcecode/repository_ir.py,sha256=9ZGRWy8g4_PH_XvdCm6i1UOHYX-qO3sxxodXytf3m0Q,320870
68
+ sourcecode/repository_ir.py,sha256=wD6ODf8nNijDIh01FWyPoY1jplpPLWvfKl_5rAvLVMQ,322680
68
69
  sourcecode/ris.py,sha256=Hw8TakTQ6hku-Abf2k8954NwkrH_sP73_8wVH8x5khc,22079
69
70
  sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
70
71
  sourcecode/scanner.py,sha256=z3CV0rcGunu0Y8mpNgp07wI7nxT0pxw1BkXRRtI0Rpo,9609
@@ -88,6 +89,8 @@ sourcecode/tree_utils.py,sha256=8GAkIfQAsvtEudIeW1l4ooH_oRtrWR8cpJQJsEa_Pfw,2093
88
89
  sourcecode/type_usage_surface.py,sha256=51IrKRQoIoRnlsiDjHnqpJBn2rc6E59aRhgS0HTzAF0,4428
89
90
  sourcecode/validation_inference.py,sha256=KN3tEBQzxNinwlo7buer6lx5Kqdn71JAs_wuu44McfU,17475
90
91
  sourcecode/validation_surface.py,sha256=A0-FVswL__XOcw92wvIwghwLq5AWtIm2kzO3RjEb1b4,27325
92
+ sourcecode/verify_edit.py,sha256=LFk5CRste11GwNUTUrTGxoQpmQhy4Qoqd7NdhGq1weA,26190
93
+ sourcecode/verify_rules.py,sha256=y7MXrjGDVed0uTY5P6hcP6AwvFGADDQ4uKanJfO9z5E,13273
91
94
  sourcecode/version_check.py,sha256=CHp6ZxTIfo8kyHPCBgJA1uFC0xQCoXMuuOfrW8QTL8o,4942
92
95
  sourcecode/workspace.py,sha256=X_6NmNnitvT3_38V-JDChydo_sR68s249hLFlrQskU0,8271
93
96
  sourcecode/detectors/__init__.py,sha256=A0AACJFF6HWf_RgatNtWu3PUzstcKtIGM9f1PoFcJug,1987
@@ -113,7 +116,7 @@ sourcecode/detectors/terraform.py,sha256=cxORPR_zVLOJpHlh4e9JnFpkQsn_UnqMMom5yG6
113
116
  sourcecode/detectors/tooling.py,sha256=8CKbtxwQoABP-WyBRNmdAmHDOvAH57AR1cF4UKuWEdQ,2074
114
117
  sourcecode/mcp/__init__.py,sha256=XU4HfRGbdid8wdUA0x_4f7uKZD1z3mv_XUY_WU_T9Mw,179
115
118
  sourcecode/mcp/orchestrator.py,sha256=diVoQgn24QmgPL3Ev8Sp6hsvh02OqY3MktHXOzrlodo,36525
116
- sourcecode/mcp/registry.py,sha256=DyREJve47GnajFYgPORmzQjH1Qi2ZzE5twvQpPXMghI,65821
119
+ sourcecode/mcp/registry.py,sha256=OCUCRLPRM176FCw850rF7dNh74f5Zj0h3a9lpxrf8mI,68171
117
120
  sourcecode/mcp/runner.py,sha256=RGimCwhLyHxjCvH7q72y12HWEnATXZLC-NBWhkzlu0I,2852
118
121
  sourcecode/mcp/server.py,sha256=yCp1YmmZ4hQE7KIHC8XcdxnToYCwSe9vxp4E-YMzVg0,63713
119
122
  sourcecode/mcp/onboarding/__init__.py,sha256=sj2PWqEBmMc4zBNkomg89WtL0M6S7A9yb7_wAuSWNP4,66
@@ -145,8 +148,8 @@ sourcecode/telemetry/consent.py,sha256=LIAO9ohJZF8OuZwM4u1VWtALlYfTCCKq4wV3Vwc7i
145
148
  sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
146
149
  sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
147
150
  sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
148
- sourcecode-2.6.2.dist-info/METADATA,sha256=7dMltG6Rgrg_RmRX5tEh13LJZIYv1ouwALVAcjHHY2I,10851
149
- sourcecode-2.6.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
150
- sourcecode-2.6.2.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
151
- sourcecode-2.6.2.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
152
- sourcecode-2.6.2.dist-info/RECORD,,
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,,