sourcecode 2.2.0__py3-none-any.whl → 2.4.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.
@@ -0,0 +1,352 @@
1
+ """Graph Evidence Provider — turns the structural module graph into reusable
2
+ topological evidence (ADR-0003, migration step 2).
3
+
4
+ Responsibility boundary (deliberately narrow):
5
+
6
+ ModuleGraph -> [ this module ] -> topological metrics -> GraphEvidence
7
+
8
+ This component knows GRAPHS, not ARCHETYPES. It does not classify, does not
9
+ emit prose verdicts, and never imports the archetype scorer. It computes a
10
+ small, robust set of topological metrics and packages each as a language- and
11
+ domain-agnostic ``GraphEvidence`` object. The archetype scorer consumes those
12
+ evidence objects; it never traverses the graph itself.
13
+
14
+ The metrics are chosen so that architectural style can emerge from STRUCTURE
15
+ rather than from directory names:
16
+
17
+ * scc_mass — fraction of nodes that sit inside a NON-TRIVIAL
18
+ strongly-connected component (an import cycle).
19
+ Range [0,1]. 0 for a clean DAG (layered/
20
+ hexagonal); high for a woven engine core.
21
+ Cost O(V+E) (iterative Tarjan).
22
+ * cyclic_density — fraction of edges that live INSIDE a non-trivial
23
+ SCC (true back-edges). Range [0,1]. Low for
24
+ layered; high for engine. Cost O(V+E).
25
+ * hub_concentration — share of all edges pointing at the single
26
+ highest fan-in node. Range [0,1]. High for
27
+ engine/microkernel cores. Cost O(V+E).
28
+ * module_centralization — Freeman centralization of the in-degree
29
+ distribution. Range [0,1]. ~1 for a star
30
+ (microkernel), ~0 for a uniform mesh. Cost O(V).
31
+ * downward_edge_ratio — fraction of edges flowing from a LESS stable to
32
+ a MORE stable node (Martin instability
33
+ I = out/(in+out)). Range [0,1]. High for
34
+ layered/hexagonal directed flow. Cost O(V+E).
35
+ * dependency_inversion_ratio— fraction of edges whose TARGET sits in the most
36
+ stable quartile (a stable, depended-upon core).
37
+ Range [0,1]. High for hexagonal/clean. O(V+E).
38
+ * fan_in_gini — Gini inequality of the in-degree distribution.
39
+ Range [0,1]. High when a few modules absorb most
40
+ dependencies (engine). Cost O(V log V).
41
+
42
+ All metrics are name-free: they are functions of the directed topology only.
43
+ """
44
+ from __future__ import annotations
45
+
46
+ from dataclasses import dataclass, field
47
+ from typing import Iterable, Optional
48
+
49
+ # Structural edge kinds that represent a real dependency between modules.
50
+ _STRUCTURAL_EDGE_KINDS = frozenset({"imports", "calls"})
51
+
52
+ # Graph-size thresholds for how much a metric can be trusted / how much mass it
53
+ # is allowed to contribute. A three-node toy graph must not speak with the same
54
+ # authority as a sixty-module one.
55
+ _MIN_NODES_FOR_METRICS = 4
56
+ _MIN_EDGES_FOR_METRICS = 3
57
+ _HIGH_CONF_NODES = 12
58
+ _MASS_SATURATION_NODES = 10.0
59
+
60
+
61
+ @dataclass(frozen=True)
62
+ class GraphEvidence:
63
+ """One topological fact, ready to be consumed by any scorer.
64
+
65
+ Archetype-agnostic: it says *what the graph looks like*, never *what the
66
+ system is*. ``value`` is the raw metric in [0,1]; the scorer decides which
67
+ archetype (if any) that value supports and with what weight."""
68
+
69
+ provider: str # always "module_graph"
70
+ kind: str # metric name, e.g. "scc_mass"
71
+ value: float # raw metric, 0..1
72
+ mass: float # 0..1 — topological coverage (graph size proxy)
73
+ confidence: str # low | medium | high — from graph size
74
+ detail: str # human-readable one-liner
75
+
76
+ def to_dict(self) -> dict:
77
+ return {
78
+ "provider": self.provider,
79
+ "kind": self.kind,
80
+ "value": round(self.value, 4),
81
+ "mass": round(self.mass, 4),
82
+ "confidence": self.confidence,
83
+ "detail": self.detail,
84
+ }
85
+
86
+
87
+ @dataclass
88
+ class GraphEvidenceResult:
89
+ """Metrics + evidence produced from a single module graph."""
90
+
91
+ node_count: int
92
+ edge_count: int
93
+ metrics: dict[str, float] = field(default_factory=dict)
94
+ evidence: list[GraphEvidence] = field(default_factory=list)
95
+ available: bool = False # False when the graph is too small to trust
96
+ confidence: str = "low" # overall provider confidence
97
+
98
+ def metric(self, kind: str, default: float = 0.0) -> float:
99
+ return self.metrics.get(kind, default)
100
+
101
+ def to_dict(self) -> dict:
102
+ return {
103
+ "node_count": self.node_count,
104
+ "edge_count": self.edge_count,
105
+ "available": self.available,
106
+ "confidence": self.confidence,
107
+ "metrics": {k: round(v, 4) for k, v in self.metrics.items()},
108
+ "evidence": [e.to_dict() for e in self.evidence],
109
+ }
110
+
111
+
112
+ class GraphEvidenceProvider:
113
+ """Transforms a module graph (nodes + directed edges) into GraphEvidence.
114
+
115
+ Stateless. Accepts anything exposing ``nodes`` and ``edges`` (a
116
+ ``schema.ModuleGraph`` does), so tests can hand it synthetic graphs without
117
+ building a repository."""
118
+
119
+ def analyze(
120
+ self,
121
+ graph: object,
122
+ *,
123
+ edge_kinds: Optional[Iterable[str]] = None,
124
+ ) -> GraphEvidenceResult:
125
+ nodes, adjacency = self._project(graph, edge_kinds)
126
+ n = len(nodes)
127
+ edges = [(u, v) for u, outs in adjacency.items() for v in outs]
128
+ e = len(edges)
129
+
130
+ result = GraphEvidenceResult(node_count=n, edge_count=e)
131
+ if n < _MIN_NODES_FOR_METRICS or e < _MIN_EDGES_FOR_METRICS:
132
+ # Too small to say anything structural. Report nothing rather than
133
+ # asserting metrics on noise.
134
+ return result
135
+
136
+ result.available = True
137
+ result.confidence = "high" if n >= _HIGH_CONF_NODES else "medium"
138
+ topo_mass = min(1.0, n / _MASS_SATURATION_NODES)
139
+
140
+ in_deg, out_deg = self._degrees(nodes, edges)
141
+ scc_of, scc_sizes = self._tarjan_scc(nodes, adjacency)
142
+ instability = self._instability(nodes, in_deg, out_deg)
143
+
144
+ m: dict[str, float] = {}
145
+ # Only NON-TRIVIAL SCCs (size > 1) count: a pure DAG has none, a woven
146
+ # engine core has many. A single-node "component" is not a cycle.
147
+ cyclic_nodes = sum(sz for sz in scc_sizes.values() if sz > 1)
148
+ m["scc_mass"] = (cyclic_nodes / n) if n else 0.0
149
+ m["cyclic_density"] = self._cyclic_density(edges, scc_of, scc_sizes, e)
150
+ m["hub_concentration"] = (max(in_deg.values(), default=0) / e) if e else 0.0
151
+ m["module_centralization"] = self._centralization(in_deg, n)
152
+ m["downward_edge_ratio"] = self._downward_ratio(edges, instability)
153
+ m["dependency_inversion_ratio"] = self._inversion_ratio(edges, instability)
154
+ m["fan_in_gini"] = self._gini([in_deg[x] for x in nodes])
155
+ result.metrics = m
156
+
157
+ # One evidence object per metric.
158
+ details = {
159
+ "scc_mass": f"{m['scc_mass']:.0%} of modules sit inside an import cycle",
160
+ "cyclic_density": f"{m['cyclic_density']:.0%} of edges are back-edges inside a cycle",
161
+ "hub_concentration": f"top module absorbs {m['hub_concentration']:.0%} of all dependencies",
162
+ "module_centralization": f"in-degree centralization {m['module_centralization']:.2f} (1=star, 0=mesh)",
163
+ "downward_edge_ratio": f"{m['downward_edge_ratio']:.0%} of edges flow toward more-stable modules",
164
+ "dependency_inversion_ratio": f"{m['dependency_inversion_ratio']:.0%} of edges point into the stable core",
165
+ "fan_in_gini": f"in-degree inequality (Gini) {m['fan_in_gini']:.2f}",
166
+ }
167
+ result.evidence = [
168
+ GraphEvidence(
169
+ provider="module_graph", kind=k, value=m[k], mass=topo_mass,
170
+ confidence=result.confidence, detail=details[k],
171
+ )
172
+ for k in details
173
+ ]
174
+ return result
175
+
176
+ # ── graph projection ───────────────────────────────────────────────────────
177
+ @staticmethod
178
+ def _project(
179
+ graph: object, edge_kinds: Optional[Iterable[str]]
180
+ ) -> "tuple[list[str], dict[str, set[str]]]":
181
+ """Reduce the graph to a deduped directed adjacency over the relevant
182
+ vertices. Prefers module-granularity nodes; falls back to all nodes."""
183
+ raw_nodes = list(getattr(graph, "nodes", []) or [])
184
+ raw_edges = list(getattr(graph, "edges", []) or [])
185
+ kinds = frozenset(edge_kinds) if edge_kinds is not None else _STRUCTURAL_EDGE_KINDS
186
+
187
+ module_ids = {getattr(nd, "id") for nd in raw_nodes if getattr(nd, "kind", None) == "module"}
188
+ # If the graph carries module nodes, restrict to them; otherwise use every
189
+ # node (synthetic test graphs, symbol-only graphs).
190
+ if module_ids:
191
+ allowed = module_ids
192
+ else:
193
+ allowed = {getattr(nd, "id") for nd in raw_nodes}
194
+
195
+ adjacency: dict[str, set[str]] = {nid: set() for nid in allowed}
196
+ for ed in raw_edges:
197
+ if getattr(ed, "kind", None) not in kinds:
198
+ continue
199
+ u = getattr(ed, "source", None)
200
+ v = getattr(ed, "target", None)
201
+ if u is None or v is None or u == v:
202
+ continue
203
+ if u not in allowed or v not in allowed:
204
+ continue
205
+ adjacency.setdefault(u, set()).add(v)
206
+ adjacency.setdefault(v, set())
207
+ nodes = sorted(adjacency.keys())
208
+ return nodes, adjacency
209
+
210
+ # ── metric primitives ──────────────────────────────────────────────────────
211
+ @staticmethod
212
+ def _degrees(
213
+ nodes: list[str], edges: list[tuple[str, str]]
214
+ ) -> "tuple[dict[str, int], dict[str, int]]":
215
+ in_deg = {x: 0 for x in nodes}
216
+ out_deg = {x: 0 for x in nodes}
217
+ for u, v in edges:
218
+ out_deg[u] += 1
219
+ in_deg[v] += 1
220
+ return in_deg, out_deg
221
+
222
+ @staticmethod
223
+ def _tarjan_scc(
224
+ nodes: list[str], adjacency: dict[str, set[str]]
225
+ ) -> "tuple[dict[str, int], dict[int, int]]":
226
+ """Iterative Tarjan. Returns (node -> scc_id, scc_id -> size)."""
227
+ index_of: dict[str, int] = {}
228
+ low: dict[str, int] = {}
229
+ on_stack: set[str] = set()
230
+ stack: list[str] = []
231
+ scc_of: dict[str, int] = {}
232
+ sizes: dict[int, int] = {}
233
+ counter = 0
234
+ scc_id = 0
235
+
236
+ for root in nodes:
237
+ if root in index_of:
238
+ continue
239
+ # work stack of (node, iterator-position)
240
+ work: list[tuple[str, int]] = [(root, 0)]
241
+ while work:
242
+ v, pi = work[-1]
243
+ if pi == 0:
244
+ index_of[v] = low[v] = counter
245
+ counter += 1
246
+ stack.append(v)
247
+ on_stack.add(v)
248
+ succ = sorted(adjacency.get(v, ()))
249
+ if pi < len(succ):
250
+ work[-1] = (v, pi + 1)
251
+ w = succ[pi]
252
+ if w not in index_of:
253
+ work.append((w, 0))
254
+ elif w in on_stack:
255
+ low[v] = min(low[v], index_of[w])
256
+ continue
257
+ # done with v
258
+ if low[v] == index_of[v]:
259
+ size = 0
260
+ while True:
261
+ w = stack.pop()
262
+ on_stack.discard(w)
263
+ scc_of[w] = scc_id
264
+ size += 1
265
+ if w == v:
266
+ break
267
+ sizes[scc_id] = size
268
+ scc_id += 1
269
+ work.pop()
270
+ if work:
271
+ parent = work[-1][0]
272
+ low[parent] = min(low[parent], low[v])
273
+ return scc_of, sizes
274
+
275
+ @staticmethod
276
+ def _cyclic_density(
277
+ edges: list[tuple[str, str]], scc_of: dict[str, int],
278
+ scc_sizes: dict[int, int], e: int,
279
+ ) -> float:
280
+ if not e:
281
+ return 0.0
282
+ intra = 0
283
+ for u, v in edges:
284
+ su = scc_of.get(u)
285
+ if su is not None and su == scc_of.get(v) and scc_sizes.get(su, 1) > 1:
286
+ intra += 1
287
+ return intra / e
288
+
289
+ @staticmethod
290
+ def _centralization(in_deg: dict[str, int], n: int) -> float:
291
+ """Freeman in-degree centralization, normalized to [0,1]."""
292
+ if n < 2:
293
+ return 0.0
294
+ mx = max(in_deg.values(), default=0)
295
+ total_diff = sum(mx - d for d in in_deg.values())
296
+ denom = (n - 1) * (n - 1) # theoretical max for a directed star
297
+ return min(1.0, total_diff / denom) if denom else 0.0
298
+
299
+ @staticmethod
300
+ def _instability(
301
+ nodes: list[str], in_deg: dict[str, int], out_deg: dict[str, int]
302
+ ) -> dict[str, float]:
303
+ """Martin instability I = out / (in + out). Isolated nodes -> None-like
304
+ (excluded via absence)."""
305
+ inst: dict[str, float] = {}
306
+ for x in nodes:
307
+ total = in_deg[x] + out_deg[x]
308
+ if total > 0:
309
+ inst[x] = out_deg[x] / total
310
+ return inst
311
+
312
+ @staticmethod
313
+ def _downward_ratio(
314
+ edges: list[tuple[str, str]], instability: dict[str, float]
315
+ ) -> float:
316
+ considered = 0
317
+ downward = 0
318
+ for u, v in edges:
319
+ if u in instability and v in instability:
320
+ considered += 1
321
+ # edge from less-stable (higher I) to more-stable (lower I)
322
+ if instability[u] >= instability[v]:
323
+ downward += 1
324
+ return (downward / considered) if considered else 0.0
325
+
326
+ @staticmethod
327
+ def _inversion_ratio(
328
+ edges: list[tuple[str, str]], instability: dict[str, float]
329
+ ) -> float:
330
+ """Fraction of edges whose target sits in the most-stable quartile."""
331
+ vals = sorted(instability.values())
332
+ if not vals or not edges:
333
+ return 0.0
334
+ q1 = vals[max(0, len(vals) // 4 - 1)]
335
+ into_core = sum(
336
+ 1 for _u, v in edges
337
+ if v in instability and instability[v] <= q1
338
+ )
339
+ return into_core / len(edges)
340
+
341
+ @staticmethod
342
+ def _gini(values: list[int]) -> float:
343
+ vals = sorted(float(v) for v in values)
344
+ n = len(vals)
345
+ s = sum(vals)
346
+ if n == 0 or s == 0:
347
+ return 0.0
348
+ cum = 0.0
349
+ for i, v in enumerate(vals, start=1):
350
+ cum += i * v
351
+ # Gini = (2*sum(i*x_i) / (n*sum)) - (n+1)/n
352
+ return max(0.0, min(1.0, (2 * cum) / (n * s) - (n + 1) / n))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 2.2.0
3
+ Version: 2.4.0
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
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
42
42
 
43
43
  **Context · Impact · Migration · Architecture · Review — everything from one structural model.**
44
44
 
45
- ![Version](https://img.shields.io/badge/version-2.2.0-blue)
45
+ ![Version](https://img.shields.io/badge/version-2.3.0-blue)
46
46
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
47
47
 
48
48
  > **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
@@ -84,7 +84,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
84
84
  # pip / pipx
85
85
  pipx install sourcecode # or: pip install sourcecode
86
86
 
87
- ask version # ask 2.2.0
87
+ ask version # ask 2.3.0
88
88
  ```
89
89
 
90
90
  > **Package vs. command.** The install package is named `sourcecode` this release
@@ -1,17 +1,19 @@
1
- sourcecode/__init__.py,sha256=QOkIfC7KA2tM2ix9_wUgS6fHiERh2v_TTNhjw8RBb1o,308
1
+ sourcecode/__init__.py,sha256=1iN-frOfJr05mYoED0IYnoxSY-4xnA-sHP9MRe_9Qn4,308
2
2
  sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
3
+ sourcecode/archetype.py,sha256=ezeyCR4E0VeZUUBPwmsWUUPAhIlqwwkc8qDieUx2JWA,31087
3
4
  sourcecode/architecture_analyzer.py,sha256=6_wC4TYuBYxaqckZS0I1vBSMRPeH2vzlDB48gXwOOd4,46627
4
- sourcecode/architecture_summary.py,sha256=UaxmUU77oTlWxttMU3c6PxHrU-Q6q5N31TeU_NWWTdU,24297
5
+ sourcecode/architecture_summary.py,sha256=pkl6lIOnSy-poa9EzW43U0MkXhx2FDfAFh8pDXLpcTo,26630
5
6
  sourcecode/ast_extractor.py,sha256=FEsYTsMCXbJfSYQZRk1k-I-PjUNGnKqO1IxMftsgA5U,51351
6
7
  sourcecode/cache.py,sha256=1V3vsaODAa2UBJAC0xpvxpmRdriCezQx5Q8JCcfgziE,31892
7
8
  sourcecode/call_surface.py,sha256=fiqYfHooxN1fX9oQoysq1LS3LoZcobhyUNjAGEZKpwk,4148
8
9
  sourcecode/caller_metrics.py,sha256=HG8RCOkpzzsEGdnMRob6byrwjoj5__t0TEsBj4yR7ks,2574
9
10
  sourcecode/canonical_ir.py,sha256=U69m8Vkr0p407xF1q5y1KguHXWEAubYw8NFHR9hDNJk,29178
10
11
  sourcecode/cir_graphs.py,sha256=9G0HHj1kw2325IDyzo2OpX73BNswEckecf4MZUXB4JM,12078
11
- sourcecode/classifier.py,sha256=YTTCoRdcLEFRVcql9Ow1dE7eYQj0jq2rgx32bRDnb1k,13852
12
- sourcecode/cli.py,sha256=CKy6zdBfecnT6reYb749pLY4NqH1_Qy7c8DHnBWamrU,293593
12
+ sourcecode/classifier.py,sha256=RhLMDR031FAiUcT3bsf2EL3KrFwl7KsWh_wXXR_Bumo,18729
13
+ sourcecode/cli.py,sha256=xYrKelvgspWuus8LM4DcRroXGydbU47CAr-KV-8oysU,299977
13
14
  sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
14
15
  sourcecode/confidence_analyzer.py,sha256=_jckZSxksV-OU38vbkxfVNBnWCtlCq8Vwfg23x1uspA,19054
16
+ sourcecode/context_cache.py,sha256=maws79rLKDwyZ7c9Q2LwlIxqZHRFlaKzErtO3mglWoA,25036
15
17
  sourcecode/context_graph.py,sha256=8MVDu06bPhvRDTgUqWEvRME2fOw34bFLf4OUILZd13I,43527
16
18
  sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
17
19
  sourcecode/context_summarizer.py,sha256=cI2TZMvEhl0BEma12VtPaX6z03ZVBAetVzuK5GaCOvg,6852
@@ -27,13 +29,14 @@ sourcecode/entrypoint_classifier.py,sha256=jhTYlyqDJH2AtdEcLVaRU3lYRTJuF8DkxVzl4
27
29
  sourcecode/env_analyzer.py,sha256=aNTyYgQk5noJDfJU6FmasmESOHfiomyJw5EvZqjy6qc,22213
28
30
  sourcecode/error_schema.py,sha256=uwosfNaSujtYm11_732Hu92z5ITV040fQDaIyefSvR4,1683
29
31
  sourcecode/evidence_provider.py,sha256=GSSL44JEaouO5AHks2sB3d1YvC9xIKIld1yBYxZpXxo,4277
30
- sourcecode/explain.py,sha256=bo4sy8qkEstIF3sIefECKRYJBK97C6cMjyzRa8USCdI,21189
32
+ sourcecode/explain.py,sha256=HnHWVTNNf9fzeR3FP-A-eXKeKZvBcUEuODgIO3rJQkQ,22312
31
33
  sourcecode/file_chunker.py,sha256=3vkM3mDQ5eE_yTPvUgjyjpGFBIjkW6_mrBmIbrylnA8,16444
32
34
  sourcecode/file_classifier.py,sha256=A0fEABqtfVu1MfoaxnPAvGpZgneGgVXlJDhT74NYXxE,15314
33
35
  sourcecode/format_contract.py,sha256=1cTNqwP8geA2hbQoBHUPgX3_vSh3l8guJT_jmgEnFF8,3466
34
36
  sourcecode/fqn_utils.py,sha256=XLU7zDkNBXz_RZkIUNfpPmp1nekWtqP-fxV92tDV1vg,2158
35
37
  sourcecode/git_analyzer.py,sha256=JStxTQXNjBWi_wLdwhsZs9mT-v50cSJIz4Agzn6Kh9I,13362
36
38
  sourcecode/graph_analyzer.py,sha256=lp0eB1PWC20BYF-GpPhAyegRpKrUKgOmXZIcZSIX_Ks,65777
39
+ sourcecode/graph_evidence.py,sha256=rENNsYRZeNstX_ExNCLlbHJAruFQwxo5d00x6wO3xwI,15030
37
40
  sourcecode/hibernate_strat.py,sha256=h0leIhlWvSjYq3F99LxvLIDLrJ-xPYxWAREG4LkqZ-4,61190
38
41
  sourcecode/jdk_exports.py,sha256=fCrlwNAXUT9gge_joq6kMnY3zJxYB2pxqy-0w3o3MJI,874
39
42
  sourcecode/license.py,sha256=keFuwNxdAtvK2Ds91Wl79GMYxuxWYnN5Wbw1qBpaoUI,24896
@@ -115,8 +118,8 @@ sourcecode/telemetry/consent.py,sha256=LIAO9ohJZF8OuZwM4u1VWtALlYfTCCKq4wV3Vwc7i
115
118
  sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
116
119
  sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
117
120
  sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
118
- sourcecode-2.2.0.dist-info/METADATA,sha256=GWUjIL4RNPYx8cMKSRHxzELL49K0anIx6qH8aHAsB3I,10837
119
- sourcecode-2.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
120
- sourcecode-2.2.0.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
121
- sourcecode-2.2.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
122
- sourcecode-2.2.0.dist-info/RECORD,,
121
+ sourcecode-2.4.0.dist-info/METADATA,sha256=fairNRCqgRAaSicMGWcWQcPral9_3NeVBIDTrv2rHaI,10837
122
+ sourcecode-2.4.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
123
+ sourcecode-2.4.0.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
124
+ sourcecode-2.4.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
125
+ sourcecode-2.4.0.dist-info/RECORD,,