diffgenome 0.1.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.
- diffgenome/__init__.py +7 -0
- diffgenome/__main__.py +240 -0
- diffgenome/_collectors/go/dg/dg.go +623 -0
- diffgenome/_collectors/go/go.mod +3 -0
- diffgenome/_collectors/go/instrument/facts.go +346 -0
- diffgenome/_collectors/go/instrument/main.go +484 -0
- diffgenome/_collectors/node/instrument.js +289 -0
- diffgenome/_collectors/node/jest-setup.js +40 -0
- diffgenome/_collectors/node/package-lock.json +35 -0
- diffgenome/_collectors/node/package.json +11 -0
- diffgenome/_collectors/node/runtime.js +426 -0
- diffgenome/ambiguity.py +122 -0
- diffgenome/api.py +67 -0
- diffgenome/change.py +86 -0
- diffgenome/change_artifact.py +310 -0
- diffgenome/collect/__init__.py +2 -0
- diffgenome/collect/go_test.py +271 -0
- diffgenome/collect/node_jest.py +319 -0
- diffgenome/collect/py_monitoring.py +985 -0
- diffgenome/collect/py_runtime.py +116 -0
- diffgenome/collect/py_symbols.py +238 -0
- diffgenome/collect/pytest_plugin.py +130 -0
- diffgenome/compose.py +469 -0
- diffgenome/dependence.py +264 -0
- diffgenome/evaluate.py +669 -0
- diffgenome/frontends/__init__.py +0 -0
- diffgenome/frontends/python_ir.py +335 -0
- diffgenome/genome.py +1016 -0
- diffgenome/genome_pipeline.py +674 -0
- diffgenome/genome_prompt.py +33 -0
- diffgenome/genome_state.py +2118 -0
- diffgenome/graph.py +426 -0
- diffgenome/llm.py +189 -0
- diffgenome/model.py +364 -0
- diffgenome/mvp.py +398 -0
- diffgenome/probe.py +509 -0
- diffgenome/projection.py +308 -0
- diffgenome/py.typed +0 -0
- diffgenome/render.py +118 -0
- diffgenome/report.py +363 -0
- diffgenome/resolve.py +37 -0
- diffgenome/runtime.py +74 -0
- diffgenome/runtime_evidence.py +261 -0
- diffgenome/sandbox.py +166 -0
- diffgenome/serialize.py +96 -0
- diffgenome/sites.py +19 -0
- diffgenome/static_types.py +69 -0
- diffgenome/structure.py +462 -0
- diffgenome-0.1.0.dist-info/METADATA +139 -0
- diffgenome-0.1.0.dist-info/RECORD +53 -0
- diffgenome-0.1.0.dist-info/WHEEL +4 -0
- diffgenome-0.1.0.dist-info/entry_points.txt +2 -0
- diffgenome-0.1.0.dist-info/licenses/LICENSE +202 -0
diffgenome/structure.py
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
"""Observed execution structure: occurrences and a generalized skeleton (Experiment 10, v3).
|
|
2
|
+
|
|
3
|
+
Call nesting and chronology are runtime facts, not semantics, so a proposer should not be
|
|
4
|
+
free to invent them. This module derives them deterministically, relative to a vocabulary
|
|
5
|
+
(the entity names a genome uses), from the existing event protocol: node ids are creation
|
|
6
|
+
order, parents precede children, and a branch observation names its call node and a `seq`
|
|
7
|
+
on the same clock. No collector change is needed.
|
|
8
|
+
|
|
9
|
+
Two objects, kept apart:
|
|
10
|
+
|
|
11
|
+
- **Occurrence graph** (evidence, one execution). Every observed call of a vocabulary
|
|
12
|
+
entity is an occurrence: its id is the call node id; its parent is the nearest ancestor
|
|
13
|
+
call that is also a vocabulary entity (other frames are looked through; None = no modeled
|
|
14
|
+
ancestor); its ordinal counts earlier siblings of the same entity under the same parent;
|
|
15
|
+
its events are its child occurrences and the branch evaluations made in its own call, in
|
|
16
|
+
time order; its outcome is the call's exit. The runtime records no call-site ids, so
|
|
17
|
+
repeated calls are keyed by (parent occurrence, entity, ordinal).
|
|
18
|
+
|
|
19
|
+
- **Skeleton** (deterministic abstraction over the shown executions). Per entity: which
|
|
20
|
+
vocabulary entities were observed as its *ancestors* and whether it was observed with no
|
|
21
|
+
modeled ancestor (ROOT), with support. Per parent entity: pairwise precedence among the
|
|
22
|
+
event families of its occurrences (child entities, and its own decision sites as
|
|
23
|
+
`site:<id>`): `x ≺ y` when, in every occurrence where both occur, every x precedes every y;
|
|
24
|
+
families that interleave in some occurrence are recorded as interleaved (a repeated
|
|
25
|
+
region). Nothing is said about why a region repeats or how often.
|
|
26
|
+
|
|
27
|
+
Checks built on these (used by `genome_state`):
|
|
28
|
+
- a predicted occurrence of B under P (or at the top of a scenario) that no observed
|
|
29
|
+
occurrence of B supports is a placement problem: B was never observed during P (or B was
|
|
30
|
+
never observed without a modeled ancestor);
|
|
31
|
+
- a predicted order x-then-y inside an occurrence of A, where `y ≺ x` held in every shown
|
|
32
|
+
occurrence of A with both, is an order problem.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
from dataclasses import dataclass, field
|
|
38
|
+
from typing import Any
|
|
39
|
+
|
|
40
|
+
from diffgenome.model import CallNode, Execution, SubstitutionNode
|
|
41
|
+
|
|
42
|
+
ROOT = "<root>"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _tail(symbol: str) -> str:
|
|
46
|
+
return symbol.split(":", 1)[1] if symbol[:3] in ("go:", "py:", "js:") else symbol
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _matches(claimed: str, symbol: str) -> bool:
|
|
50
|
+
t, c = _tail(symbol), _tail(claimed)
|
|
51
|
+
return t == c or t.endswith("." + c)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def canonical(vocab: list[str], symbol: str) -> str | None:
|
|
55
|
+
"""The vocabulary name an observed symbol belongs to; the longest match wins."""
|
|
56
|
+
hits = [v for v in vocab if _matches(v, symbol)]
|
|
57
|
+
return max(hits, key=len) if hits else None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _node_symbol(n: Any) -> str:
|
|
61
|
+
if isinstance(n, CallNode):
|
|
62
|
+
return n.symbol
|
|
63
|
+
if isinstance(n, SubstitutionNode):
|
|
64
|
+
return n.claimed_target or ""
|
|
65
|
+
return ""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class Occurrence:
|
|
70
|
+
id: int # the call node id
|
|
71
|
+
entity: str # vocabulary name
|
|
72
|
+
parent: int | None # parent occurrence id (nearest modeled ancestor), None at the top
|
|
73
|
+
ancestors: list[str] # vocabulary entities of every modeled ancestor, nearest first
|
|
74
|
+
ordinal: int # earlier siblings of the same entity under the same parent
|
|
75
|
+
first: int
|
|
76
|
+
last: int # the occurrence covers node ids / branch seqs in [first, last]
|
|
77
|
+
events: list[tuple[str, str, Any]] = field(default_factory=list)
|
|
78
|
+
# ("call", <entity>, <child occurrence id>) | ("branch", "site:<id>", <outcome>)
|
|
79
|
+
outcome: str = "unknown"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def occurrences(ex: Execution, vocab: list[str]) -> dict[int, Occurrence]:
|
|
83
|
+
nodes = ex.nodes
|
|
84
|
+
name: dict[int, str] = {}
|
|
85
|
+
for n in nodes:
|
|
86
|
+
c = canonical(vocab, _node_symbol(n))
|
|
87
|
+
if c is not None:
|
|
88
|
+
name[n.id] = c
|
|
89
|
+
last = {n.id: n.id for n in nodes}
|
|
90
|
+
for n in reversed(nodes): # a subtree's last id, children before parents
|
|
91
|
+
if n.parent is not None:
|
|
92
|
+
last[n.parent] = max(last[n.parent], last[n.id])
|
|
93
|
+
out: dict[int, Occurrence] = {}
|
|
94
|
+
counts: dict[tuple[int | None, str], int] = {}
|
|
95
|
+
for n in nodes: # ids are creation order: parents come first
|
|
96
|
+
if n.id not in name:
|
|
97
|
+
continue
|
|
98
|
+
p = n.parent
|
|
99
|
+
chain: list[int] = []
|
|
100
|
+
while p is not None:
|
|
101
|
+
if p in name:
|
|
102
|
+
chain.append(p)
|
|
103
|
+
p = nodes[p].parent
|
|
104
|
+
parent = chain[0] if chain else None
|
|
105
|
+
k = (parent, name[n.id])
|
|
106
|
+
ordinal = counts.get(k, 0)
|
|
107
|
+
counts[k] = ordinal + 1
|
|
108
|
+
occ = Occurrence(
|
|
109
|
+
n.id, name[n.id], parent, [name[a] for a in chain], ordinal, n.id, last[n.id],
|
|
110
|
+
outcome=getattr(n, "outcome", "unknown"),
|
|
111
|
+
) # fmt: skip
|
|
112
|
+
out[n.id] = occ
|
|
113
|
+
# events in time order: a child occurrence at its node id; a branch, which belongs to
|
|
114
|
+
# the occurrence of its own call node, at its seq (a branch at seq s precedes node s)
|
|
115
|
+
times: dict[int, list[tuple[int, int, tuple[str, str, Any]]]] = {o: [] for o in out}
|
|
116
|
+
for occ in out.values():
|
|
117
|
+
if occ.parent is not None:
|
|
118
|
+
times[occ.parent].append((occ.id, 1, ("call", occ.entity, occ.id)))
|
|
119
|
+
for b in ex.branches:
|
|
120
|
+
if b.node in out:
|
|
121
|
+
times[b.node].append((b.seq, 0, ("branch", f"site:{b.site}", b.outcome)))
|
|
122
|
+
for oid, evs in times.items():
|
|
123
|
+
out[oid].events = [e for _, _, e in sorted(evs, key=lambda t: (t[0], t[1]))]
|
|
124
|
+
return out
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass
|
|
128
|
+
class Skeleton:
|
|
129
|
+
"""Deterministic structure over several executions (see module doc)."""
|
|
130
|
+
|
|
131
|
+
executions: int = 0
|
|
132
|
+
# entity -> {ancestor entity | ROOT: number of executions where observed so}
|
|
133
|
+
ancestry: dict[str, dict[str, int]] = field(default_factory=dict)
|
|
134
|
+
# entity -> {nearest modeled parent | ROOT: executions}
|
|
135
|
+
parents: dict[str, dict[str, int]] = field(default_factory=dict)
|
|
136
|
+
# parent entity -> {(x, y): [occurrences with both, executions with both]} where x ≺ y
|
|
137
|
+
before: dict[str, dict[tuple[str, str], list[int]]] = field(default_factory=dict)
|
|
138
|
+
# parent entity -> pairs that interleave in at least one occurrence
|
|
139
|
+
interleaved: dict[str, set[frozenset[str]]] = field(default_factory=dict)
|
|
140
|
+
# parent entity -> family -> [occurrences containing it, max count in one occurrence]
|
|
141
|
+
families: dict[str, dict[str, list[int]]] = field(default_factory=dict)
|
|
142
|
+
# entity -> number of occurrences observed
|
|
143
|
+
seen: dict[str, int] = field(default_factory=dict)
|
|
144
|
+
# entity -> occurrences of it observed with each modeled ancestor (any depth)
|
|
145
|
+
inside_occ: dict[str, dict[str, int]] = field(default_factory=dict)
|
|
146
|
+
# site -> {entity | ROOT: evaluations}: the occurrence evaluating it and its ancestors
|
|
147
|
+
site_during: dict[str, dict[str, int]] = field(default_factory=dict)
|
|
148
|
+
# site -> {entity: evaluations}: the occurrence evaluating it in its own call
|
|
149
|
+
site_owner: dict[str, dict[str, int]] = field(default_factory=dict)
|
|
150
|
+
# parent entity -> repeated regions: {"members": [...], "head": family | None,
|
|
151
|
+
# "within": {(x, y): segments} (x before y inside one repetition), "max_repeats": n}
|
|
152
|
+
regions: dict[str, list[dict[str, Any]]] = field(default_factory=dict)
|
|
153
|
+
|
|
154
|
+
def observed_inside(self, entity: str, parent: str) -> bool:
|
|
155
|
+
"""Some observed occurrence of `entity` ran during an occurrence of `parent`
|
|
156
|
+
(`parent` among its modeled ancestors, any depth); ROOT: with no modeled ancestor."""
|
|
157
|
+
return self.ancestry.get(entity, {}).get(parent, 0) > 0
|
|
158
|
+
|
|
159
|
+
def always_during(self, entity: str, enclosing: str) -> bool:
|
|
160
|
+
"""Every observed occurrence of `entity` ran during `enclosing` (and there is one)."""
|
|
161
|
+
n = self.seen.get(entity, 0)
|
|
162
|
+
return n > 0 and self.inside_occ.get(entity, {}).get(enclosing, 0) == n
|
|
163
|
+
|
|
164
|
+
def strictly_before(self, parent: str, x: str, y: str) -> bool:
|
|
165
|
+
"""x ≺ y in every shown occurrence of `parent` containing both (and at least one)."""
|
|
166
|
+
return (x, y) in self.before.get(parent, {}) and frozenset((x, y)) not in (
|
|
167
|
+
self.interleaved.get(parent, set())
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def build_skeleton(executions: list[Execution], vocab: list[str]) -> Skeleton:
|
|
172
|
+
sk = Skeleton(executions=len(executions))
|
|
173
|
+
for ex in executions:
|
|
174
|
+
occs = occurrences(ex, vocab)
|
|
175
|
+
anc_seen: dict[str, set[str]] = {}
|
|
176
|
+
par_seen: dict[str, set[str]] = {}
|
|
177
|
+
pair_exec: dict[str, set[tuple[str, str]]] = {}
|
|
178
|
+
for o in occs.values():
|
|
179
|
+
sk.seen[o.entity] = sk.seen.get(o.entity, 0) + 1
|
|
180
|
+
io = sk.inside_occ.setdefault(o.entity, {})
|
|
181
|
+
for a in set(o.ancestors) or {ROOT}:
|
|
182
|
+
io[a] = io.get(a, 0) + 1
|
|
183
|
+
for kind, fam, _ in o.events:
|
|
184
|
+
if kind == "branch":
|
|
185
|
+
so = sk.site_owner.setdefault(fam[5:], {})
|
|
186
|
+
so[o.entity] = so.get(o.entity, 0) + 1
|
|
187
|
+
sd = sk.site_during.setdefault(fam[5:], {})
|
|
188
|
+
for a in {o.entity, *o.ancestors}:
|
|
189
|
+
sd[a] = sd.get(a, 0) + 1
|
|
190
|
+
anc_seen.setdefault(o.entity, set()).update(o.ancestors or [ROOT])
|
|
191
|
+
par_seen.setdefault(o.entity, set()).add(
|
|
192
|
+
occs[o.parent].entity if o.parent is not None else ROOT
|
|
193
|
+
)
|
|
194
|
+
positions: dict[str, list[int]] = {}
|
|
195
|
+
for i, (_, fam, _) in enumerate(o.events):
|
|
196
|
+
positions.setdefault(fam, []).append(i)
|
|
197
|
+
fams = sk.families.setdefault(o.entity, {})
|
|
198
|
+
for fam, pos in positions.items():
|
|
199
|
+
rec = fams.setdefault(fam, [0, 0])
|
|
200
|
+
rec[0] += 1
|
|
201
|
+
rec[1] = max(rec[1], len(pos))
|
|
202
|
+
names = sorted(positions)
|
|
203
|
+
for i, x in enumerate(names):
|
|
204
|
+
for y in names[i + 1 :]:
|
|
205
|
+
px, py = positions[x], positions[y]
|
|
206
|
+
if max(px) < min(py):
|
|
207
|
+
key = (x, y)
|
|
208
|
+
elif max(py) < min(px):
|
|
209
|
+
key = (y, x)
|
|
210
|
+
else:
|
|
211
|
+
sk.interleaved.setdefault(o.entity, set()).add(frozenset((x, y)))
|
|
212
|
+
continue
|
|
213
|
+
rec2 = sk.before.setdefault(o.entity, {}).setdefault(key, [0, 0])
|
|
214
|
+
rec2[0] += 1
|
|
215
|
+
pair_exec.setdefault(o.entity, set()).add(key)
|
|
216
|
+
for e, s in anc_seen.items():
|
|
217
|
+
for a in s:
|
|
218
|
+
sk.ancestry.setdefault(e, {})[a] = sk.ancestry.setdefault(e, {}).get(a, 0) + 1
|
|
219
|
+
for e, s in par_seen.items():
|
|
220
|
+
for p in s:
|
|
221
|
+
sk.parents.setdefault(e, {})[p] = sk.parents.setdefault(e, {}).get(p, 0) + 1
|
|
222
|
+
for parent, keys in pair_exec.items():
|
|
223
|
+
for key in keys:
|
|
224
|
+
sk.before[parent][key][1] += 1
|
|
225
|
+
# a pair observed in both orders (in different occurrences) is not an order
|
|
226
|
+
for parent, pairs in sk.before.items():
|
|
227
|
+
for x, y in list(pairs):
|
|
228
|
+
if (y, x) in pairs:
|
|
229
|
+
sk.interleaved.setdefault(parent, set()).add(frozenset((x, y)))
|
|
230
|
+
all_occs = [occurrences(ex, vocab) for ex in executions]
|
|
231
|
+
flat = [o for occs in all_occs for o in occs.values()]
|
|
232
|
+
for parent, pairs_i in sk.interleaved.items():
|
|
233
|
+
for members in _components(pairs_i):
|
|
234
|
+
sk.regions.setdefault(parent, []).append(_segment(parent, members, flat))
|
|
235
|
+
# a family that repeats inside one occurrence without interleaving with another is a
|
|
236
|
+
# one-member repeated region: it starts every repetition itself
|
|
237
|
+
for parent, fams in sk.families.items():
|
|
238
|
+
in_region = {m for r in sk.regions.get(parent, []) for m in r["members"]}
|
|
239
|
+
for fam, (_, max_n) in fams.items():
|
|
240
|
+
if max_n > 1 and fam not in in_region:
|
|
241
|
+
sk.regions.setdefault(parent, []).append(
|
|
242
|
+
{"members": [fam], "head": fam, "within": {}, "max_repeats": max_n}
|
|
243
|
+
)
|
|
244
|
+
return sk
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _components(pairs: set[frozenset[str]]) -> list[list[str]]:
|
|
248
|
+
"""Families connected by interleaving: one repeated region each."""
|
|
249
|
+
adj: dict[str, set[str]] = {}
|
|
250
|
+
for pair in pairs:
|
|
251
|
+
a, b = sorted(pair)
|
|
252
|
+
adj.setdefault(a, set()).add(b)
|
|
253
|
+
adj.setdefault(b, set()).add(a)
|
|
254
|
+
seen: set[str] = set()
|
|
255
|
+
out: list[list[str]] = []
|
|
256
|
+
for start in sorted(adj):
|
|
257
|
+
if start in seen:
|
|
258
|
+
continue
|
|
259
|
+
comp, stack = [], [start]
|
|
260
|
+
while stack:
|
|
261
|
+
x = stack.pop()
|
|
262
|
+
if x in seen:
|
|
263
|
+
continue
|
|
264
|
+
seen.add(x)
|
|
265
|
+
comp.append(x)
|
|
266
|
+
stack.extend(adj[x] - seen)
|
|
267
|
+
out.append(sorted(comp))
|
|
268
|
+
return out
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _segment(parent: str, members: list[str], occs: list[Occurrence]) -> dict[str, Any]:
|
|
272
|
+
"""A repeated region's head is a member that starts every repetition: in every
|
|
273
|
+
occurrence of `parent`, the region's events begin with it and, between consecutive
|
|
274
|
+
heads, every other member appears at most once. With a head, the order of members
|
|
275
|
+
inside one repetition is recorded (x before y in every segment holding both). Without
|
|
276
|
+
one, only membership is known."""
|
|
277
|
+
mine = [o for o in occs if o.entity == parent]
|
|
278
|
+
seqs = [[f for _, f, _ in o.events if f in members] for o in mine]
|
|
279
|
+
seqs = [q for q in seqs if q]
|
|
280
|
+
|
|
281
|
+
def segments(head: str, seq: list[str]) -> list[list[str]] | None:
|
|
282
|
+
if seq[0] != head:
|
|
283
|
+
return None
|
|
284
|
+
segs: list[list[str]] = []
|
|
285
|
+
for f in seq:
|
|
286
|
+
if f == head:
|
|
287
|
+
segs.append([f])
|
|
288
|
+
elif f in segs[-1]:
|
|
289
|
+
return None
|
|
290
|
+
else:
|
|
291
|
+
segs[-1].append(f)
|
|
292
|
+
return segs
|
|
293
|
+
|
|
294
|
+
for head in members:
|
|
295
|
+
split = [segments(head, q) for q in seqs]
|
|
296
|
+
if not seqs or any(sp is None for sp in split):
|
|
297
|
+
continue
|
|
298
|
+
segs = [seg for sp in split for seg in sp or []]
|
|
299
|
+
within: dict[tuple[str, str], int] = {}
|
|
300
|
+
bad: set[tuple[str, str]] = set()
|
|
301
|
+
for seg in segs:
|
|
302
|
+
for i, x in enumerate(seg):
|
|
303
|
+
for y in seg[i + 1 :]:
|
|
304
|
+
within[(x, y)] = within.get((x, y), 0) + 1
|
|
305
|
+
bad.add((y, x))
|
|
306
|
+
return {
|
|
307
|
+
"members": members,
|
|
308
|
+
"head": head,
|
|
309
|
+
"within": {k: v for k, v in within.items() if k not in bad},
|
|
310
|
+
"max_repeats": max(len(sp or []) for sp in split),
|
|
311
|
+
}
|
|
312
|
+
return {"members": members, "head": None, "within": {}, "max_repeats": None}
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _within_problems(e: str, fams: list[str], sk: Skeleton) -> list[str]:
|
|
316
|
+
out: list[str] = []
|
|
317
|
+
for region in sk.regions.get(e, []):
|
|
318
|
+
head, members = region["head"], region["members"]
|
|
319
|
+
if head is None:
|
|
320
|
+
continue
|
|
321
|
+
segs: list[list[str]] = []
|
|
322
|
+
for f in fams:
|
|
323
|
+
if f not in members:
|
|
324
|
+
continue
|
|
325
|
+
if f == head or not segs:
|
|
326
|
+
segs.append([f])
|
|
327
|
+
else:
|
|
328
|
+
segs[-1].append(f)
|
|
329
|
+
for seg in segs:
|
|
330
|
+
for i, x in enumerate(seg):
|
|
331
|
+
for y in seg[i + 1 :]:
|
|
332
|
+
if x != y and (y, x) in region["within"]:
|
|
333
|
+
out.append(
|
|
334
|
+
f"inside {e}, one repetition: {x} predicted before {y}, observed after"
|
|
335
|
+
)
|
|
336
|
+
return out
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _executions_with(sk: Skeleton, parent: str, fam: str) -> int:
|
|
340
|
+
"""Executions in which `fam` was observed inside `parent` (children: by ancestry; own
|
|
341
|
+
decision sites: by the site's owner)."""
|
|
342
|
+
if fam.startswith("site:"):
|
|
343
|
+
return sk.executions if parent in sk.site_owner.get(fam[5:], {}) else 0
|
|
344
|
+
return sk.parents.get(fam, {}).get(parent, 0)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def placement_problems(pred_occurrences: list[dict[str, Any]], sk: Skeleton) -> list[str]:
|
|
348
|
+
"""Problems of a predicted occurrence tree against the skeleton. Each predicted
|
|
349
|
+
occurrence: {"entity", "parent": index | None, "events": [(kind, family), ...]}.
|
|
350
|
+
Only entities the skeleton has observed are judged; nothing unobserved is assumed."""
|
|
351
|
+
problems: list[str] = []
|
|
352
|
+
for occ in pred_occurrences:
|
|
353
|
+
e = occ["entity"]
|
|
354
|
+
if sk.seen.get(e, 0) == 0:
|
|
355
|
+
continue
|
|
356
|
+
parent = pred_occurrences[occ["parent"]]["entity"] if occ["parent"] is not None else ROOT
|
|
357
|
+
if not sk.observed_inside(e, parent):
|
|
358
|
+
where = "outside every modeled caller" if parent == ROOT else f"during {parent}"
|
|
359
|
+
problems.append(f"{e} placed {where}, never observed so")
|
|
360
|
+
for kind, fam in occ["events"]:
|
|
361
|
+
during = sk.site_during.get(fam[5:]) if kind == "branch" else None
|
|
362
|
+
if during is not None and e not in during:
|
|
363
|
+
problems.append(f"{fam} predicted during {e}, never evaluated there")
|
|
364
|
+
fams = [f for _, f in occ["events"]]
|
|
365
|
+
# a family never observed repeating inside this entity (once at most in every
|
|
366
|
+
# occurrence, across >= 2 executions) must not be predicted repeating there; members of
|
|
367
|
+
# a repeated region are exempt (their count follows the supplied occurrences)
|
|
368
|
+
in_regions = {m for r in sk.regions.get(e, []) for m in r["members"]}
|
|
369
|
+
for fam in set(fams) - in_regions:
|
|
370
|
+
rec = sk.families.get(e, {}).get(fam)
|
|
371
|
+
if rec and rec[1] == 1 and fams.count(fam) > 1 and _executions_with(sk, e, fam) >= 2:
|
|
372
|
+
problems.append(
|
|
373
|
+
f"{fam} predicted {fams.count(fam)} times inside one {e}, "
|
|
374
|
+
"never observed repeating"
|
|
375
|
+
)
|
|
376
|
+
for i, x in enumerate(fams):
|
|
377
|
+
for y in fams[i + 1 :]:
|
|
378
|
+
if x != y and sk.strictly_before(e, y, x):
|
|
379
|
+
problems.append(f"inside {e}: {x} predicted before {y}, observed after")
|
|
380
|
+
problems += _within_problems(e, fams, sk)
|
|
381
|
+
return sorted(set(problems))
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def render(sk: Skeleton, focus: list[str] | None = None) -> str:
|
|
385
|
+
"""Text for a proposer: containment and phase order, with support."""
|
|
386
|
+
lines = [f"(from {sk.executions} shown execution(s))"]
|
|
387
|
+
for e in sorted(sk.parents):
|
|
388
|
+
if focus and e not in focus:
|
|
389
|
+
continue
|
|
390
|
+
par = ", ".join(f"{p} (in {n} exec)" for p, n in sorted(sk.parents[e].items()))
|
|
391
|
+
anc = ", ".join(f"{a} (in {n} exec)" for a, n in sorted(sk.ancestry.get(e, {}).items()))
|
|
392
|
+
lines.append(f"- {e}: {sk.seen.get(e, 0)} occurrence(s)")
|
|
393
|
+
lines.append(f" nearest modeled caller: {par}")
|
|
394
|
+
lines.append(f" runs during: {anc}")
|
|
395
|
+
for parent in sorted(sk.families):
|
|
396
|
+
if focus and parent not in focus:
|
|
397
|
+
continue
|
|
398
|
+
fams = sk.families[parent]
|
|
399
|
+
if len(fams) < 2:
|
|
400
|
+
continue
|
|
401
|
+
lines.append(f"\ninside {parent}:")
|
|
402
|
+
for fam, (n_occ, max_n) in sorted(fams.items()):
|
|
403
|
+
rep = f"repeated (up to {max_n} in one occurrence)" if max_n > 1 else "once"
|
|
404
|
+
lines.append(f" family {fam}: in {n_occ} occurrence(s), {rep}")
|
|
405
|
+
for (x, y), (n_occ, n_ex) in sorted(sk.before.get(parent, {}).items()):
|
|
406
|
+
if sk.strictly_before(parent, x, y):
|
|
407
|
+
lines.append(f" {x} BEFORE {y} (all of the first before all of the second;")
|
|
408
|
+
lines.append(f" {n_occ} occurrence(s), {n_ex} execution(s))")
|
|
409
|
+
for region in sk.regions.get(parent, []):
|
|
410
|
+
lines.append(f" REPEATED REGION: {', '.join(region['members'])}")
|
|
411
|
+
if region["head"] is None:
|
|
412
|
+
lines.append(" (no member starts every repetition: only membership is known)")
|
|
413
|
+
continue
|
|
414
|
+
lines.append(
|
|
415
|
+
f" each repetition starts with {region['head']} "
|
|
416
|
+
f"(up to {region['max_repeats']} repetitions in one occurrence); inside one:"
|
|
417
|
+
)
|
|
418
|
+
for (x, y), n in sorted(region["within"].items()):
|
|
419
|
+
lines.append(f" {x} BEFORE {y} ({n} repetition(s))")
|
|
420
|
+
return "\n".join(lines)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def resolve(vocab: list[str], name: str) -> str:
|
|
424
|
+
"""A genome/scenario entity name to the vocabulary name it denotes (itself if none)."""
|
|
425
|
+
hits = [v for v in vocab if v == name or v.endswith("." + name) or name.endswith("." + v)]
|
|
426
|
+
return max(hits, key=len) if hits else name
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def repetitions(
|
|
430
|
+
occs: dict[int, Occurrence], occ_id: int, members: list[str], head: str
|
|
431
|
+
) -> list[dict[str, Any]]:
|
|
432
|
+
"""The observed repetitions of a repeated region inside one occurrence: the region's
|
|
433
|
+
member events split at each `head` event. Each repetition lists its own events, the
|
|
434
|
+
occurrences it contains (child calls and all their modeled descendants), and every
|
|
435
|
+
branch evaluation inside it (the enclosing call's own, and those of the contained
|
|
436
|
+
occurrences). Its identity is its ordinal."""
|
|
437
|
+
reps: list[dict[str, Any]] = []
|
|
438
|
+
for kind, fam, ref in occs[occ_id].events:
|
|
439
|
+
if fam not in members:
|
|
440
|
+
continue
|
|
441
|
+
if fam == head or not reps:
|
|
442
|
+
reps.append({"events": [], "occurrences": [], "branches": []})
|
|
443
|
+
rep = reps[-1]
|
|
444
|
+
rep["events"].append((kind, fam, ref))
|
|
445
|
+
if kind == "branch":
|
|
446
|
+
rep["branches"].append((fam[5:], ref))
|
|
447
|
+
else:
|
|
448
|
+
rep["occurrences"].append(ref)
|
|
449
|
+
children: dict[int, list[int]] = {}
|
|
450
|
+
for o in occs.values():
|
|
451
|
+
if o.parent is not None:
|
|
452
|
+
children.setdefault(o.parent, []).append(o.id)
|
|
453
|
+
for rep in reps:
|
|
454
|
+
stack = list(rep["occurrences"])
|
|
455
|
+
seen: list[int] = []
|
|
456
|
+
while stack:
|
|
457
|
+
o = stack.pop(0)
|
|
458
|
+
seen.append(o)
|
|
459
|
+
rep["branches"] += [(f[5:], v) for k, f, v in occs[o].events if k == "branch"]
|
|
460
|
+
stack.extend(children.get(o, []))
|
|
461
|
+
rep["occurrences"] = seen
|
|
462
|
+
return reps
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: diffgenome
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Runtime evidence for code changes: run a repository's existing tests against a change and report what actually executed.
|
|
5
|
+
Author: karims
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: change impact,code review,runtime evidence,test mapping,tracing
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
16
|
+
Classifier: Topic :: Software Development :: Testing
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Provides-Extra: genome
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# diffgenome
|
|
23
|
+
|
|
24
|
+
**Runtime evidence for code changes.** diffgenome runs a repository's *existing* tests against a
|
|
25
|
+
change, records what actually executes, and reports it as a small, versioned JSON contract
|
|
26
|
+
(`diffgenome-runtime/1`) that a reviewer or a review tool can consume.
|
|
27
|
+
|
|
28
|
+
It answers questions static analysis and models can only guess at:
|
|
29
|
+
|
|
30
|
+
- Which changed functions did the existing tests actually execute, and in **exactly which tests
|
|
31
|
+
and subtests**?
|
|
32
|
+
- Through which call chains did execution reach the changed code, including dynamic dispatch,
|
|
33
|
+
registries and decorators the static call graph cannot follow?
|
|
34
|
+
- Which **outcomes** of each changed condition were observed (true / false / never evaluated)?
|
|
35
|
+
- How did the changed code exit (returned, returned-error, raised `<type>`), and with which
|
|
36
|
+
argument type shapes?
|
|
37
|
+
- Where did observation stop: mocks, fakes, eager task runners, external systems?
|
|
38
|
+
- Which changed behavior did **no existing test exercise**?
|
|
39
|
+
|
|
40
|
+
No model is involved. Everything reported was observed, or is a static decision site needed to
|
|
41
|
+
place an observed outcome on a changed line.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install diffgenome
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Python 3.12+ (the Python tracer uses `sys.monitoring`). No runtime dependencies. Language
|
|
50
|
+
support:
|
|
51
|
+
|
|
52
|
+
| target | tests run with | needs |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| Python | pytest | the target's virtualenv (`--python`) |
|
|
55
|
+
| Go | `go test` | a Go toolchain with the module's dependencies available offline |
|
|
56
|
+
| Node / TypeScript | Jest | the target's `node_modules` (TypeScript is taken from there, or from `DIFFGENOME_NODE_PATH`) |
|
|
57
|
+
|
|
58
|
+
Tests run inside a disposable copy of the repository (never the checkout itself), under a
|
|
59
|
+
sandbox with no network unless `--allow-loopback` is given for local test services.
|
|
60
|
+
|
|
61
|
+
## Use
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Python: the change between two revisions, the tests that cover it
|
|
65
|
+
diffgenome change --runtime python --repo . --python .venv/bin/python \
|
|
66
|
+
--source-root src --test-root tests --diff main..HEAD --out .diffgenome/run
|
|
67
|
+
|
|
68
|
+
# Go
|
|
69
|
+
diffgenome change --runtime go --repo . --test-root api --tests ./api/ \
|
|
70
|
+
--mock-dir db/mock --diff main..HEAD --out .diffgenome/run
|
|
71
|
+
|
|
72
|
+
# analyze a revision other than the checkout, without touching the checkout
|
|
73
|
+
diffgenome change ... --rev <head> --diff <base>..<head>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The result is `.diffgenome/run/diffgenome-change.json`. Its `runtime` section is the contract:
|
|
77
|
+
|
|
78
|
+
```jsonc
|
|
79
|
+
{
|
|
80
|
+
"format": "diffgenome-runtime/1",
|
|
81
|
+
"universe": { "test_scope": "tests/", "executions": 55, "passed": 55, "failed": 0, "tests": [...] },
|
|
82
|
+
"changed_functions": [{
|
|
83
|
+
"symbol": "py:app.rows.handler.RowHandler.restore_row", "file": "...", "line": 3221, "origin": "repo",
|
|
84
|
+
"executed": true, "calls": 3, "tests": ["...::test_editor_can_redo_create_row", "..."],
|
|
85
|
+
"exits": { "returned": 3 }, "raised_inside": {}, "arg_shapes": [[["user", "User"], ["row_id", "int"]]],
|
|
86
|
+
"callers": [{ "symbol": "...DeleteRowActionType.undo", "calls": 2, "tests": 2 }],
|
|
87
|
+
"stand_ins": {},
|
|
88
|
+
"changed_sites": [{ "line": 3240, "predicate": "view is None", "true": 0, "false": 3 }]
|
|
89
|
+
}],
|
|
90
|
+
"edges": [{ "caller": {...}, "callee": {...}, "executions": 2, "tests": [...], "tests_total": 2 }],
|
|
91
|
+
"boundaries": [{ "kind": "external", "caller": "...", "target": "celery.canvas._chord" }],
|
|
92
|
+
"gaps": [
|
|
93
|
+
{ "kind": "function_not_executed", "symbol": "...TrashHandler.restore_item" },
|
|
94
|
+
{ "kind": "branch_outcome_not_observed", "line": 217, "predicate": "not fields", "outcome": "true" },
|
|
95
|
+
{ "kind": "stand_in_reached", "target": "...FormulaFieldType.run_periodic_update" }
|
|
96
|
+
],
|
|
97
|
+
"not_reported": ["values (only type shapes and opaque fingerprints are recorded)",
|
|
98
|
+
"except-handler coverage (handlers are not decision sites)", "per-line coverage"]
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Every "not executed" is relative to `universe.test_scope`: the tests that were run. Locations
|
|
103
|
+
carry `origin` (`repo`, `test`, `external`) so a consumer can tell application frames from test
|
|
104
|
+
frames.
|
|
105
|
+
|
|
106
|
+
The artifact's other sections (`nodes`, `edges`, `executions`, `boundaries`, `facts`) describe
|
|
107
|
+
the observed behavioral neighborhood of the change; `report.md` and `behavioral-map.md` render
|
|
108
|
+
it for people.
|
|
109
|
+
|
|
110
|
+
## Consumers
|
|
111
|
+
|
|
112
|
+
[Sydes](https://pypi.org/project/sydes/) uses the runtime contract for change verification:
|
|
113
|
+
observed call edges join its call graph for reachability, the exact tests per changed function
|
|
114
|
+
become supporting test evidence, the gaps become verification gaps, and AI recovery is only asked
|
|
115
|
+
about what the runtime evidence cannot answer. Sydes depends on the contract alone, not on
|
|
116
|
+
diffgenome internals.
|
|
117
|
+
|
|
118
|
+
## Optional: the Behavioral Genome (research)
|
|
119
|
+
|
|
120
|
+
`diffgenome genome` builds a semantic layer on top of the runtime evidence: a model proposes
|
|
121
|
+
behavioral rules (variables, decisions, transitions), and a deterministic checker keeps only the
|
|
122
|
+
rules the observed executions verify or support. It is optional research code: nothing in
|
|
123
|
+
`diffgenome change` or the runtime contract uses it, and it needs no extra packages (an OpenAI
|
|
124
|
+
key only for live proposals, read from `OPENAI_API_KEY` or `~/.config/diffgenome/openai_api_key`).
|
|
125
|
+
Install with `pip install "diffgenome[genome]"` to state the intent. See
|
|
126
|
+
`docs/research-overview.md` and `docs/runtime-evaluation.md` for what has and has not been
|
|
127
|
+
shown to work.
|
|
128
|
+
|
|
129
|
+
## Development
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
uv sync
|
|
133
|
+
uv run pytest
|
|
134
|
+
uv run ruff check src tests && uv run mypy src
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
Apache-2.0. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
diffgenome/__init__.py,sha256=uHLh7NN0pCqtu9aLkiP3A_0mXjWFhB64vu1QmyBGc08,253
|
|
2
|
+
diffgenome/__main__.py,sha256=KKvfmaKIB1aX9Q5DxuAbczJoIHQKZtzWFLTZqdQTYQ4,9419
|
|
3
|
+
diffgenome/ambiguity.py,sha256=eCameI11iaCAa9d8otPcgXwr1EY6zKZf0vSZdPLqPFc,5133
|
|
4
|
+
diffgenome/api.py,sha256=y3HnwFfLRn0jfBWyvncEWTObi8WXiDaAlfE3bAIUhaU,2327
|
|
5
|
+
diffgenome/change.py,sha256=ZudIM5-7HE0i9qxsV0iHNNRrqLzZI534wNWv5ifbE9g,2858
|
|
6
|
+
diffgenome/change_artifact.py,sha256=LeIWGqw7g93pv8QG54WrmpyhfplKa1YdP5DWFF3vXWA,12320
|
|
7
|
+
diffgenome/compose.py,sha256=r_HnkvP-Q9Uhj8mEXN8pQyBHIUckS84VWh8mxNmYwT0,20701
|
|
8
|
+
diffgenome/dependence.py,sha256=26erLyayHn2_8g-PCFi4ATKzd5zw_2hIfompRARJvcY,11240
|
|
9
|
+
diffgenome/evaluate.py,sha256=G3bucl2YTjJ4PSvGcTBX8Lvsw8efrbl-iypRxOYHjp0,26999
|
|
10
|
+
diffgenome/genome.py,sha256=tDujpj1yNVt1vrCmbLRrt1gPEOBcHHCCuKi_LLN9XKI,41337
|
|
11
|
+
diffgenome/genome_pipeline.py,sha256=IO0CJQTxqmCmhvfhPnYMJBPqpYxPzjeRuEMtNEB-mls,27392
|
|
12
|
+
diffgenome/genome_prompt.py,sha256=RNMI-27Zxyd2KYkvPCORhwIkeRY46o7UrRddtOfnXJQ,12565
|
|
13
|
+
diffgenome/genome_state.py,sha256=YKoDGeiRJAHiXrBOyGVWYj3IIMINt0SPFx77WznRm-M,95495
|
|
14
|
+
diffgenome/graph.py,sha256=dapLj9RrC4MZVHuIX5_PSGo3m_f5E_rv5mnUR1k-4ho,17505
|
|
15
|
+
diffgenome/llm.py,sha256=sEbrWQh0BU3q_MFtRznFXYC6-EJiEl-tYriQv-J-40c,7658
|
|
16
|
+
diffgenome/model.py,sha256=n0SRTcJN3sVa1Xb6_5im5fXAwydY-UT7YelhAxL5rPY,16892
|
|
17
|
+
diffgenome/mvp.py,sha256=w_4nIRvkVBo894IMkogzlOOQAwdVJDd4rynIUOn5a7M,18259
|
|
18
|
+
diffgenome/probe.py,sha256=z8bgXLb03vY7VuP6j5nE_ohXK2IkVLCnUD1vTSwWJ4c,21632
|
|
19
|
+
diffgenome/projection.py,sha256=aq3eMJDn0agMzZp0UhsKXemQi7wJwu1PKGVOrie4Q-0,13622
|
|
20
|
+
diffgenome/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
diffgenome/render.py,sha256=FUrgzduqrW-l5BClD2GsqfDLaI3-gOYbYzs7Z2bDooM,4290
|
|
22
|
+
diffgenome/report.py,sha256=qVdkIihAcT2f7oLapwlyvsuhOOi4XcBB7DY__HpoXAo,14511
|
|
23
|
+
diffgenome/resolve.py,sha256=8Mi-t3odMfyo0gbSLBYWOoir7rkzo1-orH2PPBt8v3g,1614
|
|
24
|
+
diffgenome/runtime.py,sha256=NH78fyfOQLKBTYyjYEcB4ygCzW4BajjF5Vh9lN3jMsE,2863
|
|
25
|
+
diffgenome/runtime_evidence.py,sha256=yT2lmcIQFfpevMTWBY0pEv-e-Ky1wZNVYQ8Z0BPOj1E,10694
|
|
26
|
+
diffgenome/sandbox.py,sha256=UcXam5kX3NHNvRsr3TkyEJw7juZMxRKidq3WBUiMFaE,5954
|
|
27
|
+
diffgenome/serialize.py,sha256=r_TwAV8nG4MGouXPaukQywvk1_cCeFUprmpYCccVrN8,2891
|
|
28
|
+
diffgenome/sites.py,sha256=sBIFhpXCROrIWVgnHf-h_wFG4vIZeRgYY_fjWsXoZ8Q,782
|
|
29
|
+
diffgenome/static_types.py,sha256=eC7Q_QdsI6OqZr20dEJpXQw24q6LcdBrrB9hAiV9UUo,2526
|
|
30
|
+
diffgenome/structure.py,sha256=KTS1F7u6ePDCmC6rcnQm6IdNEwZHwZlW2kJw5Fw-TU0,21631
|
|
31
|
+
diffgenome/_collectors/go/go.mod,sha256=J6kKccdD9gBbwDUOF-PhWiHINXd0OaB3VFfvK7kI-OE,46
|
|
32
|
+
diffgenome/_collectors/go/dg/dg.go,sha256=KVDTghtEcEHO7YSOLJV6y0f7bar9AKTfX9Z9YLXRrXU,15746
|
|
33
|
+
diffgenome/_collectors/go/instrument/facts.go,sha256=4WMxZQ-U06JQ_wY63gjY_4w1wiLRCFRsDThfAl5TpVc,9918
|
|
34
|
+
diffgenome/_collectors/go/instrument/main.go,sha256=nYXJfltYmmPPKUcRwmBm_4zSLqpT3eDCygC3WENnI5s,16160
|
|
35
|
+
diffgenome/_collectors/node/instrument.js,sha256=CuY3N76_BteLS5eoBpt2MUxCZaNie96lhaGER7MnJ2E,15831
|
|
36
|
+
diffgenome/_collectors/node/jest-setup.js,sha256=8UR3tTFt3pdBVoBeIvZgymkxQL0DoyjbA11lfIas4ZY,1633
|
|
37
|
+
diffgenome/_collectors/node/package-lock.json,sha256=1xmoRedhHsUgusGKpO4INkAd9nzc1vzhG6D2llTZok4,1024
|
|
38
|
+
diffgenome/_collectors/node/package.json,sha256=wEJ3sTIAtcMR280U0Ox7cPZyWL0i06Cx2rcb3pOwpkU,301
|
|
39
|
+
diffgenome/_collectors/node/runtime.js,sha256=M3ed2lgvPxsjn6CanbgRLjZ1isEABuJ9a4fFQCL36oQ,15866
|
|
40
|
+
diffgenome/collect/__init__.py,sha256=Xj42bDNrmR6XK1RWL8t1-ITtQMTecSu1GT-YhC4Od5w,174
|
|
41
|
+
diffgenome/collect/go_test.py,sha256=8zhYoDNPpSqdgbVyGxCsLcC9g-VTjH-VYJoIbqCW8Ig,11387
|
|
42
|
+
diffgenome/collect/node_jest.py,sha256=nWHIe6XWIRA82fnBAv-HvuYUGBkTdC0o1QfySRLozLA,13245
|
|
43
|
+
diffgenome/collect/py_monitoring.py,sha256=P8f6i1OZjPEtGz6bY4pyRrYgPvCCYTQICdgfrp631Rs,43498
|
|
44
|
+
diffgenome/collect/py_runtime.py,sha256=IQg5Zrtp-dZh358Edt_lv7mLGJl62l1IPZaZn39AlTw,5184
|
|
45
|
+
diffgenome/collect/py_symbols.py,sha256=ta3AiA9ZKzwYZnR3CDAo6zhzdOBH4W0Uhi2vband7OY,10004
|
|
46
|
+
diffgenome/collect/pytest_plugin.py,sha256=-74eLK9z53s1FaBC9k3dkG7c8jAvOynL_St6KwToHck,5329
|
|
47
|
+
diffgenome/frontends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
48
|
+
diffgenome/frontends/python_ir.py,sha256=E4bz9GGMQepZTlcxlSdWMu23pGzjNCBDdee8ye_jTTw,12993
|
|
49
|
+
diffgenome-0.1.0.dist-info/METADATA,sha256=GW4Fx7NXe8mmMq4cH_LpXnBIXDdiXKwCo3jwIzBMSRI,6227
|
|
50
|
+
diffgenome-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
51
|
+
diffgenome-0.1.0.dist-info/entry_points.txt,sha256=X2ICPUevqUGSr_AYBw33wpDApTEMl3hOnr6ib5PxN7Q,56
|
|
52
|
+
diffgenome-0.1.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
53
|
+
diffgenome-0.1.0.dist-info/RECORD,,
|