fable-engine 1.3.1__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.
- fable_compressor.py +356 -0
- fable_engine/__init__.py +1 -0
- fable_engine/actions/__init__.py +291 -0
- fable_engine/actions/cas.py +182 -0
- fable_engine/actions/deliberation.py +523 -0
- fable_engine/actions/fleet.py +807 -0
- fable_engine/actions/lifecycle.py +298 -0
- fable_engine/actions/scrapers.py +116 -0
- fable_engine/actions/system3.py +815 -0
- fable_engine/browser.py +824 -0
- fable_engine/cas.py +974 -0
- fable_engine/fable_session.json +510 -0
- fable_engine/guards.py +283 -0
- fable_engine/schema.py +714 -0
- fable_engine/scrapers/__init__.py +32 -0
- fable_engine/scrapers/arxiv.py +115 -0
- fable_engine/scrapers/base.py +386 -0
- fable_engine/scrapers/github.py +129 -0
- fable_engine/scrapers/reddit.py +154 -0
- fable_engine/scrapers/web.py +120 -0
- fable_engine/scrapers/x.py +125 -0
- fable_engine/scrapers/youtube.py +132 -0
- fable_engine/server.py +414 -0
- fable_engine/session.py +1819 -0
- fable_engine/test_server.py +1362 -0
- fable_engine/updater.py +541 -0
- fable_engine-1.3.1.dist-info/LICENSE +22 -0
- fable_engine-1.3.1.dist-info/METADATA +173 -0
- fable_engine-1.3.1.dist-info/RECORD +104 -0
- fable_engine-1.3.1.dist-info/WHEEL +5 -0
- fable_engine-1.3.1.dist-info/entry_points.txt +5 -0
- fable_engine-1.3.1.dist-info/top_level.txt +6 -0
- fable_mode/__init__.py +3 -0
- fable_mode/__main__.py +4 -0
- fable_mode/adapters.py +1014 -0
- fable_mode/installer.py +553 -0
- fable_mode/launcher.py +437 -0
- fable_mode/manifest.py +142 -0
- fable_mode/resources.json +114 -0
- fable_mode/safety.py +103 -0
- fable_mode_entry.py +10 -0
- fable_v2/__init__.py +146 -0
- fable_v2/adapters.py +151 -0
- fable_v2/coder_fleet/__init__.py +100 -0
- fable_v2/coder_fleet/ast_tools.py +158 -0
- fable_v2/coder_fleet/compute.py +199 -0
- fable_v2/coder_fleet/design_engine.py +1316 -0
- fable_v2/coder_fleet/diagnostics.py +293 -0
- fable_v2/coder_fleet/fleet_dispatcher.py +214 -0
- fable_v2/coder_fleet/mock_auditor.py +306 -0
- fable_v2/coder_fleet/mutation.py +216 -0
- fable_v2/coder_fleet/property_oracle.py +260 -0
- fable_v2/coder_fleet/receipt_attestor.py +122 -0
- fable_v2/coder_fleet/red_team_swarm.py +908 -0
- fable_v2/coder_fleet/test_harness.py +198 -0
- fable_v2/coder_fleet/vector_engine.py +1287 -0
- fable_v2/coder_fleet/visual.py +357 -0
- fable_v2/coder_fleet/workspace.py +153 -0
- fable_v2/cortical/__init__.py +20 -0
- fable_v2/cortical/plasticity_engine.py +992 -0
- fable_v2/execution_broker.py +811 -0
- fable_v2/proof_engine.py +1141 -0
- fable_v2/protocol.py +485 -0
- fable_v2/runtime.py +1010 -0
- fable_v2/system3/__init__.py +204 -0
- fable_v2/system3/causal.py +558 -0
- fable_v2/system3/dialectical.py +577 -0
- fable_v2/system3/evolution.py +503 -0
- fable_v2/system3/executive.py +338 -0
- fable_v2/system3/free_energy.py +479 -0
- fable_v2/system3/hyperbolic.py +555 -0
- fable_v2/system3/induction.py +336 -0
- fable_v2/system3/kripke.py +548 -0
- fable_v2/system3/oracle.py +745 -0
- fable_v2/verifiers.py +72 -0
- tests/__init__.py +1 -0
- tests/test_anti_loop_circuit_breaker.py +64 -0
- tests/test_auto_updater.py +407 -0
- tests/test_coder_fleet.py +535 -0
- tests/test_delegation_compiler.py +54 -0
- tests/test_descriptor_boundaries.py +126 -0
- tests/test_design_engine.py +603 -0
- tests/test_epistemic_evidence_validator.py +66 -0
- tests/test_execution_broker.py +233 -0
- tests/test_fable_v2.py +406 -0
- tests/test_fleet_transitions.py +116 -0
- tests/test_fsm_redteam_evolution.py +406 -0
- tests/test_goal_rubric_and_pipeline.py +367 -0
- tests/test_hebbian_plasticity.py +585 -0
- tests/test_packaging_runtime.py +194 -0
- tests/test_proof_engine.py +259 -0
- tests/test_red_team_swarm.py +645 -0
- tests/test_redteam_remediation.py +169 -0
- tests/test_registration_transaction.py +375 -0
- tests/test_requested_regressions.py +467 -0
- tests/test_scrapers.py +370 -0
- tests/test_server_actions.py +93 -0
- tests/test_server_frontier_actions.py +269 -0
- tests/test_server_protocol.py +88 -0
- tests/test_stealth_browser.py +970 -0
- tests/test_system3.py +381 -0
- tests/test_system3_deep_integration.py +385 -0
- tests/test_system3_frontier.py +436 -0
- tests/test_vector_engine.py +608 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"""Visual Grounding Engine for Vector / SVG Analysis and Perceptual Diffing.
|
|
2
|
+
|
|
3
|
+
Pure-Python implementation using xml.etree.ElementTree with zero external C-dependencies.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
import xml.etree.ElementTree as ET
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class VisualGroundingEngine:
|
|
13
|
+
"""Engine for parsing, perceptually analyzing, and verifying vector SVG assets."""
|
|
14
|
+
|
|
15
|
+
# SVG XML namespace prefix pattern
|
|
16
|
+
_NS_REGEX = re.compile(r"^\{.*?\}")
|
|
17
|
+
|
|
18
|
+
# Color regex patterns
|
|
19
|
+
_HEX_COLOR_REGEX = re.compile(r"#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\b")
|
|
20
|
+
_RGB_COLOR_REGEX = re.compile(r"rgba?\([^)]+\)", re.IGNORECASE)
|
|
21
|
+
_HSL_COLOR_REGEX = re.compile(r"hsla?\([^)]+\)", re.IGNORECASE)
|
|
22
|
+
_OKLCH_COLOR_REGEX = re.compile(r"oklch\([^)]+\)", re.IGNORECASE)
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def _clean_tag(cls, raw_tag: str) -> str:
|
|
26
|
+
"""Remove XML namespace URI from tag name."""
|
|
27
|
+
return cls._NS_REGEX.sub("", raw_tag).lower()
|
|
28
|
+
|
|
29
|
+
def render_vector(self, code: str, format: str = "svg") -> dict[str, Any]:
|
|
30
|
+
"""Parse vector/SVG code, validate XML tags, extract viewBox, paths, and element counts.
|
|
31
|
+
|
|
32
|
+
Returns rendering metadata dict.
|
|
33
|
+
"""
|
|
34
|
+
if not code or not code.strip():
|
|
35
|
+
return {
|
|
36
|
+
"valid": False,
|
|
37
|
+
"format": format,
|
|
38
|
+
"error": "Empty vector code provided",
|
|
39
|
+
"elements_count": 0,
|
|
40
|
+
"element_types": {},
|
|
41
|
+
"paths": [],
|
|
42
|
+
"warnings": ["Empty input string"],
|
|
43
|
+
"metadata": {},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
# Parse XML
|
|
48
|
+
root = ET.fromstring(code.strip())
|
|
49
|
+
except ET.ParseError as err:
|
|
50
|
+
return {
|
|
51
|
+
"valid": False,
|
|
52
|
+
"format": format,
|
|
53
|
+
"error": f"XML parse error: {err}",
|
|
54
|
+
"elements_count": 0,
|
|
55
|
+
"element_types": {},
|
|
56
|
+
"paths": [],
|
|
57
|
+
"warnings": [f"Malformed XML syntax at line {err.position[0] if hasattr(err, 'position') else 'unknown'}"],
|
|
58
|
+
"metadata": {},
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
root_tag = self._clean_tag(root.tag)
|
|
62
|
+
warnings: list[str] = []
|
|
63
|
+
if root_tag != "svg":
|
|
64
|
+
warnings.append(f"Root tag is <{root_tag}>, expected <svg>")
|
|
65
|
+
|
|
66
|
+
# Extract viewBox
|
|
67
|
+
view_box_raw = root.attrib.get("viewBox") or root.attrib.get("viewbox")
|
|
68
|
+
view_box: list[float] | None = None
|
|
69
|
+
if view_box_raw:
|
|
70
|
+
try:
|
|
71
|
+
parts = [float(p) for p in re.split(r"[\s,]+", view_box_raw.strip()) if p]
|
|
72
|
+
if len(parts) == 4:
|
|
73
|
+
view_box = parts
|
|
74
|
+
else:
|
|
75
|
+
warnings.append(f"Invalid viewBox components: {view_box_raw}")
|
|
76
|
+
except ValueError:
|
|
77
|
+
warnings.append(f"Non-numeric viewBox values: {view_box_raw}")
|
|
78
|
+
|
|
79
|
+
width = root.attrib.get("width")
|
|
80
|
+
height = root.attrib.get("height")
|
|
81
|
+
|
|
82
|
+
# Traverse elements
|
|
83
|
+
element_types: dict[str, int] = {}
|
|
84
|
+
paths: list[str] = []
|
|
85
|
+
total_elements = 0
|
|
86
|
+
|
|
87
|
+
for elem in root.iter():
|
|
88
|
+
tag = self._clean_tag(elem.tag)
|
|
89
|
+
total_elements += 1
|
|
90
|
+
element_types[tag] = element_types.get(tag, 0) + 1
|
|
91
|
+
|
|
92
|
+
if tag == "path":
|
|
93
|
+
d_attr = elem.attrib.get("d")
|
|
94
|
+
if d_attr:
|
|
95
|
+
paths.append(d_attr.strip())
|
|
96
|
+
|
|
97
|
+
# Path complexity calculation (count of path commands: M, L, C, S, Q, T, A, Z, etc.)
|
|
98
|
+
total_commands = sum(len(re.findall(r"[MmLlHhVvCcSsQqTtAaZz]", p)) for p in paths)
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
"valid": True,
|
|
102
|
+
"format": format,
|
|
103
|
+
"viewBox": view_box,
|
|
104
|
+
"width": width,
|
|
105
|
+
"height": height,
|
|
106
|
+
"elements_count": total_elements,
|
|
107
|
+
"element_types": element_types,
|
|
108
|
+
"paths": paths,
|
|
109
|
+
"warnings": warnings,
|
|
110
|
+
"metadata": {
|
|
111
|
+
"root_tag": root_tag,
|
|
112
|
+
"path_count": len(paths),
|
|
113
|
+
"total_path_commands": total_commands,
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
def perceptual_diff(self, svg_code: str, target_spec: dict[str, Any]) -> dict[str, Any]:
|
|
118
|
+
"""Calculate coverage, element counts, path complexity, and similarity score (0.0 to 1.0)."""
|
|
119
|
+
render_res = self.render_vector(svg_code)
|
|
120
|
+
if not render_res["valid"]:
|
|
121
|
+
return {
|
|
122
|
+
"similarity_score": 0.0,
|
|
123
|
+
"element_counts": {},
|
|
124
|
+
"target_element_counts": target_spec.get("expected_counts", {}),
|
|
125
|
+
"path_complexity": 0,
|
|
126
|
+
"coverage": 0.0,
|
|
127
|
+
"diff_details": [f"Invalid SVG: {render_res.get('error')}"],
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
element_counts = render_res["element_types"]
|
|
131
|
+
path_complexity = render_res["metadata"]["total_path_commands"]
|
|
132
|
+
diff_details: list[str] = []
|
|
133
|
+
|
|
134
|
+
sub_scores: list[float] = []
|
|
135
|
+
|
|
136
|
+
# 1. Element type coverage
|
|
137
|
+
expected_types = target_spec.get("expected_types", [])
|
|
138
|
+
if expected_types:
|
|
139
|
+
matched_types = sum(1 for t in expected_types if element_counts.get(t, 0) > 0)
|
|
140
|
+
type_coverage = matched_types / len(expected_types)
|
|
141
|
+
sub_scores.append(type_coverage)
|
|
142
|
+
for t in expected_types:
|
|
143
|
+
if element_counts.get(t, 0) == 0:
|
|
144
|
+
diff_details.append(f"Missing expected element type <{t}>")
|
|
145
|
+
else:
|
|
146
|
+
sub_scores.append(1.0)
|
|
147
|
+
|
|
148
|
+
# 2. Minimum element count requirement
|
|
149
|
+
min_elements = target_spec.get("min_elements", 1)
|
|
150
|
+
actual_count = render_res["elements_count"]
|
|
151
|
+
if actual_count >= min_elements:
|
|
152
|
+
sub_scores.append(1.0)
|
|
153
|
+
else:
|
|
154
|
+
sub_scores.append(actual_count / max(1, min_elements))
|
|
155
|
+
diff_details.append(f"Element count {actual_count} is less than minimum {min_elements}")
|
|
156
|
+
|
|
157
|
+
# 3. Minimum paths requirement
|
|
158
|
+
min_paths = target_spec.get("min_paths", 0)
|
|
159
|
+
actual_paths = len(render_res["paths"])
|
|
160
|
+
if min_paths > 0:
|
|
161
|
+
if actual_paths >= min_paths:
|
|
162
|
+
sub_scores.append(1.0)
|
|
163
|
+
else:
|
|
164
|
+
sub_scores.append(actual_paths / min_paths)
|
|
165
|
+
diff_details.append(f"Path count {actual_paths} is less than minimum {min_paths}")
|
|
166
|
+
|
|
167
|
+
# 4. Palette match if expected palette is specified
|
|
168
|
+
expected_palette = target_spec.get("palette", [])
|
|
169
|
+
if expected_palette:
|
|
170
|
+
extracted = self.extract_palette_and_boxes(svg_code)
|
|
171
|
+
actual_colors = set(c.lower() for c in extracted["palette"]["all_colors"])
|
|
172
|
+
matched_colors = 0
|
|
173
|
+
for c in expected_palette:
|
|
174
|
+
if c.lower() in actual_colors:
|
|
175
|
+
matched_colors += 1
|
|
176
|
+
else:
|
|
177
|
+
diff_details.append(f"Expected color '{c}' not found in SVG palette")
|
|
178
|
+
sub_scores.append(matched_colors / len(expected_palette))
|
|
179
|
+
|
|
180
|
+
# 5. ViewBox match if specified
|
|
181
|
+
target_vb = target_spec.get("viewBox")
|
|
182
|
+
if target_vb:
|
|
183
|
+
actual_vb = render_res["viewBox"]
|
|
184
|
+
if actual_vb == target_vb:
|
|
185
|
+
sub_scores.append(1.0)
|
|
186
|
+
else:
|
|
187
|
+
sub_scores.append(0.5 if actual_vb else 0.0)
|
|
188
|
+
diff_details.append(f"viewBox {actual_vb} does not match expected {target_vb}")
|
|
189
|
+
|
|
190
|
+
similarity_score = sum(sub_scores) / len(sub_scores) if sub_scores else 1.0
|
|
191
|
+
similarity_score = round(max(0.0, min(1.0, similarity_score)), 4)
|
|
192
|
+
coverage = round(sum(1.0 for s in sub_scores if s >= 0.99) / max(1, len(sub_scores)), 4)
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
"similarity_score": similarity_score,
|
|
196
|
+
"element_counts": element_counts,
|
|
197
|
+
"target_element_counts": target_spec.get("expected_counts", {}),
|
|
198
|
+
"path_complexity": path_complexity,
|
|
199
|
+
"coverage": coverage,
|
|
200
|
+
"diff_details": diff_details,
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
def extract_palette_and_boxes(self, svg_code: str) -> dict[str, Any]:
|
|
204
|
+
"""Extract color fills/strokes (hex/rgb/hsl/oklch) and bounding geometry."""
|
|
205
|
+
if not svg_code or not svg_code.strip():
|
|
206
|
+
return {
|
|
207
|
+
"palette": {"fills": [], "strokes": [], "all_colors": []},
|
|
208
|
+
"bounding_boxes": [],
|
|
209
|
+
"total_elements": 0,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
root = ET.fromstring(svg_code.strip())
|
|
214
|
+
except ET.ParseError:
|
|
215
|
+
return {
|
|
216
|
+
"palette": {"fills": [], "strokes": [], "all_colors": []},
|
|
217
|
+
"bounding_boxes": [],
|
|
218
|
+
"total_elements": 0,
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
fills: set[str] = set()
|
|
222
|
+
strokes: set[str] = set()
|
|
223
|
+
all_colors: set[str] = set()
|
|
224
|
+
boxes: list[dict[str, Any]] = []
|
|
225
|
+
total_elements = 0
|
|
226
|
+
|
|
227
|
+
def _extract_colors_from_string(text: str) -> list[str]:
|
|
228
|
+
results = []
|
|
229
|
+
for match in self._HEX_COLOR_REGEX.finditer(text):
|
|
230
|
+
results.append(match.group(0))
|
|
231
|
+
for match in self._RGB_COLOR_REGEX.finditer(text):
|
|
232
|
+
results.append(match.group(0))
|
|
233
|
+
for match in self._HSL_COLOR_REGEX.finditer(text):
|
|
234
|
+
results.append(match.group(0))
|
|
235
|
+
for match in self._OKLCH_COLOR_REGEX.finditer(text):
|
|
236
|
+
results.append(match.group(0))
|
|
237
|
+
return results
|
|
238
|
+
|
|
239
|
+
def _clean_color_value(val: str | None) -> str | None:
|
|
240
|
+
if not val:
|
|
241
|
+
return None
|
|
242
|
+
val = val.strip()
|
|
243
|
+
if val.lower() in ("none", "transparent", "currentcolor", "inherit"):
|
|
244
|
+
return None
|
|
245
|
+
return val
|
|
246
|
+
|
|
247
|
+
for elem in root.iter():
|
|
248
|
+
tag = self._clean_tag(elem.tag)
|
|
249
|
+
total_elements += 1
|
|
250
|
+
attrib = elem.attrib
|
|
251
|
+
|
|
252
|
+
# Fill attribute
|
|
253
|
+
f_val = _clean_color_value(attrib.get("fill"))
|
|
254
|
+
if f_val:
|
|
255
|
+
fills.add(f_val)
|
|
256
|
+
all_colors.add(f_val)
|
|
257
|
+
|
|
258
|
+
# Stroke attribute
|
|
259
|
+
s_val = _clean_color_value(attrib.get("stroke"))
|
|
260
|
+
if s_val:
|
|
261
|
+
strokes.add(s_val)
|
|
262
|
+
all_colors.add(s_val)
|
|
263
|
+
|
|
264
|
+
# Inline style attribute
|
|
265
|
+
style_attr = attrib.get("style", "")
|
|
266
|
+
if style_attr:
|
|
267
|
+
style_colors = _extract_colors_from_string(style_attr)
|
|
268
|
+
for sc in style_colors:
|
|
269
|
+
all_colors.add(sc)
|
|
270
|
+
for part in style_attr.split(";"):
|
|
271
|
+
if ":" in part:
|
|
272
|
+
prop, _, v = part.partition(":")
|
|
273
|
+
prop = prop.strip().lower()
|
|
274
|
+
v_clean = _clean_color_value(v)
|
|
275
|
+
if v_clean:
|
|
276
|
+
if prop == "fill":
|
|
277
|
+
fills.add(v_clean)
|
|
278
|
+
elif prop == "stroke":
|
|
279
|
+
strokes.add(v_clean)
|
|
280
|
+
|
|
281
|
+
# Extract bounding geometry
|
|
282
|
+
box_info: dict[str, Any] | None = None
|
|
283
|
+
if tag == "rect":
|
|
284
|
+
try:
|
|
285
|
+
box_info = {
|
|
286
|
+
"tag": "rect",
|
|
287
|
+
"x": float(attrib.get("x", 0)),
|
|
288
|
+
"y": float(attrib.get("y", 0)),
|
|
289
|
+
"width": float(attrib.get("width", 0)),
|
|
290
|
+
"height": float(attrib.get("height", 0)),
|
|
291
|
+
}
|
|
292
|
+
except ValueError:
|
|
293
|
+
pass
|
|
294
|
+
elif tag == "circle":
|
|
295
|
+
try:
|
|
296
|
+
cx = float(attrib.get("cx", 0))
|
|
297
|
+
cy = float(attrib.get("cy", 0))
|
|
298
|
+
r = float(attrib.get("r", 0))
|
|
299
|
+
box_info = {
|
|
300
|
+
"tag": "circle",
|
|
301
|
+
"x": cx - r,
|
|
302
|
+
"y": cy - r,
|
|
303
|
+
"width": 2 * r,
|
|
304
|
+
"height": 2 * r,
|
|
305
|
+
"cx": cx,
|
|
306
|
+
"cy": cy,
|
|
307
|
+
"r": r,
|
|
308
|
+
}
|
|
309
|
+
except ValueError:
|
|
310
|
+
pass
|
|
311
|
+
elif tag == "ellipse":
|
|
312
|
+
try:
|
|
313
|
+
cx = float(attrib.get("cx", 0))
|
|
314
|
+
cy = float(attrib.get("cy", 0))
|
|
315
|
+
rx = float(attrib.get("rx", 0))
|
|
316
|
+
ry = float(attrib.get("ry", 0))
|
|
317
|
+
box_info = {
|
|
318
|
+
"tag": "ellipse",
|
|
319
|
+
"x": cx - rx,
|
|
320
|
+
"y": cy - ry,
|
|
321
|
+
"width": 2 * rx,
|
|
322
|
+
"height": 2 * ry,
|
|
323
|
+
"cx": cx,
|
|
324
|
+
"cy": cy,
|
|
325
|
+
"rx": rx,
|
|
326
|
+
"ry": ry,
|
|
327
|
+
}
|
|
328
|
+
except ValueError:
|
|
329
|
+
pass
|
|
330
|
+
elif tag == "line":
|
|
331
|
+
try:
|
|
332
|
+
x1 = float(attrib.get("x1", 0))
|
|
333
|
+
y1 = float(attrib.get("y1", 0))
|
|
334
|
+
x2 = float(attrib.get("x2", 0))
|
|
335
|
+
y2 = float(attrib.get("y2", 0))
|
|
336
|
+
box_info = {
|
|
337
|
+
"tag": "line",
|
|
338
|
+
"x": min(x1, x2),
|
|
339
|
+
"y": min(y1, y2),
|
|
340
|
+
"width": abs(x2 - x1),
|
|
341
|
+
"height": abs(y2 - y1),
|
|
342
|
+
}
|
|
343
|
+
except ValueError:
|
|
344
|
+
pass
|
|
345
|
+
|
|
346
|
+
if box_info:
|
|
347
|
+
boxes.append(box_info)
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
"palette": {
|
|
351
|
+
"fills": sorted(fills),
|
|
352
|
+
"strokes": sorted(strokes),
|
|
353
|
+
"all_colors": sorted(all_colors),
|
|
354
|
+
},
|
|
355
|
+
"bounding_boxes": boxes,
|
|
356
|
+
"total_elements": total_elements,
|
|
357
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Atomic Workspace Engine for In-Memory File Checkpoints, Diffs, and Rollbacks.
|
|
2
|
+
|
|
3
|
+
Provides atomic snapshotting, diff telemetry, rollback facilities, and SHA-256 milestone receipts.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import copy
|
|
8
|
+
import difflib
|
|
9
|
+
import hashlib
|
|
10
|
+
import time
|
|
11
|
+
import uuid
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AtomicWorkspaceEngine:
|
|
16
|
+
"""Engine for atomic file snapshotting, patch inspection, rollbacks, and milestone commits."""
|
|
17
|
+
|
|
18
|
+
def __init__(self) -> None:
|
|
19
|
+
self._checkpoints: dict[str, dict[str, str]] = {}
|
|
20
|
+
self._milestones: dict[str, dict[str, Any]] = {}
|
|
21
|
+
self._metadata: dict[str, dict[str, Any]] = {}
|
|
22
|
+
|
|
23
|
+
def create_checkpoint(self, task_id: str, files: dict[str, str]) -> str:
|
|
24
|
+
"""Snapshot files into in-memory storage with a unique checkpoint_id."""
|
|
25
|
+
chk_id = f"chk_{task_id}_{uuid.uuid4().hex[:8]}"
|
|
26
|
+
self._checkpoints[chk_id] = copy.deepcopy(files)
|
|
27
|
+
self._metadata[chk_id] = {
|
|
28
|
+
"task_id": task_id,
|
|
29
|
+
"created_at": time.time(),
|
|
30
|
+
"file_count": len(files),
|
|
31
|
+
"status": "active",
|
|
32
|
+
}
|
|
33
|
+
return chk_id
|
|
34
|
+
|
|
35
|
+
def inspect_patch(self, checkpoint_id: str, current_files: dict[str, str]) -> dict[str, Any]:
|
|
36
|
+
"""Generate diff statistics (added, modified, deleted lines) between checkpoint and current files."""
|
|
37
|
+
if checkpoint_id not in self._checkpoints:
|
|
38
|
+
raise KeyError(f"Checkpoint '{checkpoint_id}' not found")
|
|
39
|
+
|
|
40
|
+
snapshot = self._checkpoints[checkpoint_id]
|
|
41
|
+
all_keys = sorted(set(snapshot.keys()) | set(current_files.keys()))
|
|
42
|
+
|
|
43
|
+
files_changed = 0
|
|
44
|
+
total_added = 0
|
|
45
|
+
total_deleted = 0
|
|
46
|
+
details: dict[str, Any] = {}
|
|
47
|
+
|
|
48
|
+
for path in all_keys:
|
|
49
|
+
old_content = snapshot.get(path)
|
|
50
|
+
new_content = current_files.get(path)
|
|
51
|
+
|
|
52
|
+
if old_content is None:
|
|
53
|
+
# Newly added file
|
|
54
|
+
new_lines = (new_content or "").splitlines(keepends=True)
|
|
55
|
+
added = len(new_lines)
|
|
56
|
+
deleted = 0
|
|
57
|
+
diff_text = "".join(difflib.unified_diff([], new_lines, fromfile="/dev/null", tofile=path))
|
|
58
|
+
details[path] = {
|
|
59
|
+
"status": "added",
|
|
60
|
+
"added": added,
|
|
61
|
+
"deleted": deleted,
|
|
62
|
+
"diff": diff_text,
|
|
63
|
+
}
|
|
64
|
+
files_changed += 1
|
|
65
|
+
total_added += added
|
|
66
|
+
|
|
67
|
+
elif new_content is None:
|
|
68
|
+
# Deleted file
|
|
69
|
+
old_lines = old_content.splitlines(keepends=True)
|
|
70
|
+
added = 0
|
|
71
|
+
deleted = len(old_lines)
|
|
72
|
+
diff_text = "".join(difflib.unified_diff(old_lines, [], fromfile=path, tofile="/dev/null"))
|
|
73
|
+
details[path] = {
|
|
74
|
+
"status": "deleted",
|
|
75
|
+
"added": added,
|
|
76
|
+
"deleted": deleted,
|
|
77
|
+
"diff": diff_text,
|
|
78
|
+
}
|
|
79
|
+
files_changed += 1
|
|
80
|
+
total_deleted += deleted
|
|
81
|
+
|
|
82
|
+
elif old_content != new_content:
|
|
83
|
+
# Modified file
|
|
84
|
+
old_lines = old_content.splitlines(keepends=True)
|
|
85
|
+
new_lines = new_content.splitlines(keepends=True)
|
|
86
|
+
diff_lines = list(difflib.unified_diff(old_lines, new_lines, fromfile=f"a/{path}", tofile=f"b/{path}"))
|
|
87
|
+
added = sum(1 for line in diff_lines if line.startswith("+") and not line.startswith("+++"))
|
|
88
|
+
deleted = sum(1 for line in diff_lines if line.startswith("-") and not line.startswith("---"))
|
|
89
|
+
diff_text = "".join(diff_lines)
|
|
90
|
+
details[path] = {
|
|
91
|
+
"status": "modified",
|
|
92
|
+
"added": added,
|
|
93
|
+
"deleted": deleted,
|
|
94
|
+
"diff": diff_text,
|
|
95
|
+
}
|
|
96
|
+
files_changed += 1
|
|
97
|
+
total_added += added
|
|
98
|
+
total_deleted += deleted
|
|
99
|
+
|
|
100
|
+
else:
|
|
101
|
+
# Unchanged
|
|
102
|
+
details[path] = {
|
|
103
|
+
"status": "unchanged",
|
|
104
|
+
"added": 0,
|
|
105
|
+
"deleted": 0,
|
|
106
|
+
"diff": "",
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
"checkpoint_id": checkpoint_id,
|
|
111
|
+
"files_changed": files_changed,
|
|
112
|
+
"lines_added": total_added,
|
|
113
|
+
"lines_deleted": total_deleted,
|
|
114
|
+
"details": details,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
def rollback(self, checkpoint_id: str) -> dict[str, str]:
|
|
118
|
+
"""Return snapshot files cleanly."""
|
|
119
|
+
if checkpoint_id not in self._checkpoints:
|
|
120
|
+
raise KeyError(f"Checkpoint '{checkpoint_id}' not found")
|
|
121
|
+
return copy.deepcopy(self._checkpoints[checkpoint_id])
|
|
122
|
+
|
|
123
|
+
def commit_milestone(self, checkpoint_id: str, message: str) -> dict[str, Any]:
|
|
124
|
+
"""Mark checkpoint as committed with SHA-256 digest."""
|
|
125
|
+
if checkpoint_id not in self._checkpoints:
|
|
126
|
+
raise KeyError(f"Checkpoint '{checkpoint_id}' not found")
|
|
127
|
+
|
|
128
|
+
snapshot = self._checkpoints[checkpoint_id]
|
|
129
|
+
hasher = hashlib.sha256()
|
|
130
|
+
|
|
131
|
+
for path in sorted(snapshot.keys()):
|
|
132
|
+
hasher.update(path.encode("utf-8"))
|
|
133
|
+
hasher.update(b"\x00")
|
|
134
|
+
hasher.update(snapshot[path].encode("utf-8"))
|
|
135
|
+
hasher.update(b"\x00")
|
|
136
|
+
|
|
137
|
+
digest = hasher.hexdigest()
|
|
138
|
+
committed_at = time.time()
|
|
139
|
+
|
|
140
|
+
milestone = {
|
|
141
|
+
"checkpoint_id": checkpoint_id,
|
|
142
|
+
"message": message,
|
|
143
|
+
"digest": digest,
|
|
144
|
+
"committed_at": committed_at,
|
|
145
|
+
"file_count": len(snapshot),
|
|
146
|
+
"status": "committed",
|
|
147
|
+
}
|
|
148
|
+
self._milestones[checkpoint_id] = milestone
|
|
149
|
+
if checkpoint_id in self._metadata:
|
|
150
|
+
self._metadata[checkpoint_id]["status"] = "committed"
|
|
151
|
+
self._metadata[checkpoint_id]["milestone"] = milestone
|
|
152
|
+
|
|
153
|
+
return milestone
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Modular Fable Part 2: Hebbian Cortical Plasticity & Lifelong Neuro-Evolutionary Engine.
|
|
2
|
+
|
|
3
|
+
Exports the core domain enumerations, antibody data structures, cortical lobe representations,
|
|
4
|
+
and the production HebbianPlasticityEngine.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from .plasticity_engine import (
|
|
9
|
+
CorticalDomain,
|
|
10
|
+
CorticalLobe,
|
|
11
|
+
HebbianPlasticityEngine,
|
|
12
|
+
HeuristicAntibody,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"CorticalDomain",
|
|
17
|
+
"CorticalLobe",
|
|
18
|
+
"HebbianPlasticityEngine",
|
|
19
|
+
"HeuristicAntibody",
|
|
20
|
+
]
|