glitchlings 0.2.5__cp312-cp312-win_amd64.whl → 0.9.3__cp312-cp312-win_amd64.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.
Files changed (85) hide show
  1. glitchlings/__init__.py +36 -17
  2. glitchlings/__main__.py +0 -1
  3. glitchlings/_zoo_rust/__init__.py +12 -0
  4. glitchlings/_zoo_rust.cp312-win_amd64.pyd +0 -0
  5. glitchlings/assets/__init__.py +180 -0
  6. glitchlings/assets/apostrofae_pairs.json +32 -0
  7. glitchlings/assets/ekkokin_homophones.json +2014 -0
  8. glitchlings/assets/hokey_assets.json +193 -0
  9. glitchlings/assets/lexemes/academic.json +1049 -0
  10. glitchlings/assets/lexemes/colors.json +1333 -0
  11. glitchlings/assets/lexemes/corporate.json +716 -0
  12. glitchlings/assets/lexemes/cyberpunk.json +22 -0
  13. glitchlings/assets/lexemes/lovecraftian.json +23 -0
  14. glitchlings/assets/lexemes/synonyms.json +3354 -0
  15. glitchlings/assets/mim1c_homoglyphs.json.gz.b64 +1064 -0
  16. glitchlings/assets/pipeline_assets.json +29 -0
  17. glitchlings/attack/__init__.py +53 -0
  18. glitchlings/attack/compose.py +299 -0
  19. glitchlings/attack/core.py +465 -0
  20. glitchlings/attack/encode.py +114 -0
  21. glitchlings/attack/metrics.py +104 -0
  22. glitchlings/attack/metrics_dispatch.py +70 -0
  23. glitchlings/attack/tokenization.py +157 -0
  24. glitchlings/auggie.py +283 -0
  25. glitchlings/compat/__init__.py +9 -0
  26. glitchlings/compat/loaders.py +355 -0
  27. glitchlings/compat/types.py +41 -0
  28. glitchlings/conf/__init__.py +41 -0
  29. glitchlings/conf/loaders.py +331 -0
  30. glitchlings/conf/schema.py +156 -0
  31. glitchlings/conf/types.py +72 -0
  32. glitchlings/config.toml +2 -0
  33. glitchlings/constants.py +59 -0
  34. glitchlings/dev/__init__.py +3 -0
  35. glitchlings/dev/docs.py +45 -0
  36. glitchlings/dlc/__init__.py +17 -3
  37. glitchlings/dlc/_shared.py +296 -0
  38. glitchlings/dlc/gutenberg.py +400 -0
  39. glitchlings/dlc/huggingface.py +37 -65
  40. glitchlings/dlc/prime.py +55 -114
  41. glitchlings/dlc/pytorch.py +98 -0
  42. glitchlings/dlc/pytorch_lightning.py +173 -0
  43. glitchlings/internal/__init__.py +16 -0
  44. glitchlings/internal/rust.py +159 -0
  45. glitchlings/internal/rust_ffi.py +432 -0
  46. glitchlings/main.py +123 -32
  47. glitchlings/runtime_config.py +24 -0
  48. glitchlings/util/__init__.py +29 -176
  49. glitchlings/util/adapters.py +65 -0
  50. glitchlings/util/keyboards.py +311 -0
  51. glitchlings/util/transcripts.py +108 -0
  52. glitchlings/zoo/__init__.py +47 -24
  53. glitchlings/zoo/assets/__init__.py +29 -0
  54. glitchlings/zoo/core.py +301 -167
  55. glitchlings/zoo/core_execution.py +98 -0
  56. glitchlings/zoo/core_planning.py +451 -0
  57. glitchlings/zoo/corrupt_dispatch.py +295 -0
  58. glitchlings/zoo/ekkokin.py +118 -0
  59. glitchlings/zoo/hokey.py +137 -0
  60. glitchlings/zoo/jargoyle.py +179 -274
  61. glitchlings/zoo/mim1c.py +106 -68
  62. glitchlings/zoo/pedant/__init__.py +107 -0
  63. glitchlings/zoo/pedant/core.py +105 -0
  64. glitchlings/zoo/pedant/forms.py +74 -0
  65. glitchlings/zoo/pedant/stones.py +74 -0
  66. glitchlings/zoo/redactyl.py +44 -175
  67. glitchlings/zoo/rng.py +259 -0
  68. glitchlings/zoo/rushmore.py +359 -116
  69. glitchlings/zoo/scannequin.py +18 -125
  70. glitchlings/zoo/transforms.py +386 -0
  71. glitchlings/zoo/typogre.py +76 -162
  72. glitchlings/zoo/validation.py +477 -0
  73. glitchlings/zoo/zeedub.py +33 -86
  74. glitchlings-0.9.3.dist-info/METADATA +334 -0
  75. glitchlings-0.9.3.dist-info/RECORD +80 -0
  76. {glitchlings-0.2.5.dist-info → glitchlings-0.9.3.dist-info}/entry_points.txt +1 -0
  77. glitchlings/zoo/_ocr_confusions.py +0 -34
  78. glitchlings/zoo/_rate.py +0 -21
  79. glitchlings/zoo/reduple.py +0 -169
  80. glitchlings-0.2.5.dist-info/METADATA +0 -490
  81. glitchlings-0.2.5.dist-info/RECORD +0 -27
  82. /glitchlings/{zoo → assets}/ocr_confusions.tsv +0 -0
  83. {glitchlings-0.2.5.dist-info → glitchlings-0.9.3.dist-info}/WHEEL +0 -0
  84. {glitchlings-0.2.5.dist-info → glitchlings-0.9.3.dist-info}/licenses/LICENSE +0 -0
  85. {glitchlings-0.2.5.dist-info → glitchlings-0.9.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,70 @@
1
+ """Pure metric dispatch functions.
2
+
3
+ This module contains pure functions for dispatching metric computations.
4
+ It does not import Rust FFI or perform any IO - it operates on already-
5
+ resolved metric functions.
6
+
7
+ Pure guarantees:
8
+ - No import side effects beyond stdlib
9
+ - No Rust FFI loading
10
+ - Deterministic dispatch logic
11
+
12
+ The impure Rust metric loading lives in metrics.py.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import Sequence, TypeGuard
18
+
19
+ TokenSequence = Sequence[str]
20
+ TokenBatch = Sequence[TokenSequence]
21
+
22
+
23
+ def is_batch(tokens: TokenSequence | TokenBatch) -> TypeGuard[TokenBatch]:
24
+ """Determine if tokens represent a batch of sequences.
25
+
26
+ An empty list is treated as an empty batch (returning True) so that
27
+ ``metric([], [])`` returns ``[]`` rather than ``0.0``. This matches
28
+ the behavior of :meth:`Attack.run` when processing empty transcripts.
29
+
30
+ Args:
31
+ tokens: Either a sequence of token strings or a batch of such sequences.
32
+
33
+ Returns:
34
+ True if tokens is a batch (list of lists), False if a single sequence.
35
+ """
36
+ if not tokens:
37
+ return True # Empty list is an empty batch
38
+
39
+ first = tokens[0]
40
+ return isinstance(first, Sequence) and not isinstance(first, (str, bytes))
41
+
42
+
43
+ def validate_batch_consistency(
44
+ original: TokenSequence | TokenBatch,
45
+ corrupted: TokenSequence | TokenBatch,
46
+ metric_name: str,
47
+ ) -> None:
48
+ """Validate that both inputs are consistently batched or single.
49
+
50
+ Args:
51
+ original: Original token sequence or batch.
52
+ corrupted: Corrupted token sequence or batch.
53
+ metric_name: Name of the metric (for error messages).
54
+
55
+ Raises:
56
+ TypeError: If one input is batched and the other isn't.
57
+ """
58
+ original_is_batch = is_batch(original)
59
+ corrupted_is_batch = is_batch(corrupted)
60
+
61
+ if original_is_batch != corrupted_is_batch:
62
+ raise TypeError(f"{metric_name} expects either both batch inputs or both single sequences")
63
+
64
+
65
+ __all__ = [
66
+ "TokenBatch",
67
+ "TokenSequence",
68
+ "is_batch",
69
+ "validate_batch_consistency",
70
+ ]
@@ -0,0 +1,157 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib.util
4
+ import zlib
5
+ from typing import Any, Protocol, Sequence
6
+
7
+ DEFAULT_TIKTOKEN_ENCODINGS = ("o200k_base", "cl100k_base")
8
+
9
+
10
+ class Tokenizer(Protocol):
11
+ def encode(self, text: str) -> tuple[list[str], list[int]]: ...
12
+
13
+ def decode(self, tokens: Sequence[str]) -> str: ...
14
+
15
+
16
+ class WhitespaceTokenizer:
17
+ def encode(self, text: str) -> tuple[list[str], list[int]]:
18
+ tokens = text.split()
19
+ # Synthetic IDs based on adler32 hash for stability
20
+ ids = [zlib.adler32(t.encode("utf-8")) & 0xFFFFFFFF for t in tokens]
21
+ return tokens, ids
22
+
23
+ def decode(self, tokens: Sequence[str]) -> str:
24
+ return " ".join(tokens)
25
+
26
+ def encode_batch(self, texts: Sequence[str]) -> list[tuple[list[str], list[int]]]:
27
+ return [self.encode(text) for text in texts]
28
+
29
+
30
+ class TiktokenTokenizer:
31
+ def __init__(self, model_name: str):
32
+ import tiktoken
33
+
34
+ self.name = model_name
35
+ try:
36
+ self.enc = tiktoken.get_encoding(model_name)
37
+ except ValueError:
38
+ self.enc = tiktoken.encoding_for_model(model_name)
39
+
40
+ def encode(self, text: str) -> tuple[list[str], list[int]]:
41
+ ids = self.enc.encode(text)
42
+ tokens = [
43
+ self.enc.decode_single_token_bytes(i).decode("utf-8", errors="replace") for i in ids
44
+ ]
45
+ return tokens, ids
46
+
47
+ def decode(self, tokens: Sequence[str], sep: str = "") -> str:
48
+ return sep.join(tokens)
49
+
50
+ def encode_batch(self, texts: Sequence[str]) -> list[tuple[list[str], list[int]]]:
51
+ id_batches = [list(batch) for batch in self.enc.encode_batch(list(texts))]
52
+ token_batches: list[list[str]] = []
53
+ for ids in id_batches:
54
+ token_batches.append(
55
+ [
56
+ self.enc.decode_single_token_bytes(i).decode("utf-8", errors="replace")
57
+ for i in ids
58
+ ]
59
+ )
60
+ return list(zip(token_batches, id_batches))
61
+
62
+
63
+ class HuggingFaceTokenizerWrapper:
64
+ def __init__(self, tokenizer_obj: Any):
65
+ self.tokenizer = tokenizer_obj
66
+
67
+ def encode(self, text: str) -> tuple[list[str], list[int]]:
68
+ # tokenizers.Tokenizer.encode returns an Encoding object
69
+ encoding = self.tokenizer.encode(text)
70
+ return encoding.tokens, encoding.ids
71
+
72
+ def decode(self, tokens: Sequence[str]) -> str:
73
+ # Use the tokenizer's decode method to properly handle model-specific
74
+ # artifacts (e.g., "##" for WordPiece, "Ġ" for BPE).
75
+ # Convert tokens to IDs first, then decode.
76
+ try:
77
+ token_ids = [self.tokenizer.token_to_id(token) for token in tokens]
78
+ # Filter out None values (tokens not in vocabulary)
79
+ valid_ids = [tid for tid in token_ids if tid is not None]
80
+ if valid_ids:
81
+ result: str = self.tokenizer.decode(valid_ids)
82
+ return result
83
+ except (AttributeError, TypeError):
84
+ pass
85
+ # Fallback: simple join without any replacements
86
+ return "".join(tokens)
87
+
88
+ def encode_batch(self, texts: Sequence[str]) -> list[tuple[list[str], list[int]]]:
89
+ encodings = self.tokenizer.encode_batch(list(texts))
90
+ return [(encoding.tokens, encoding.ids) for encoding in encodings]
91
+
92
+
93
+ def resolve_tokenizer(tokenizer: str | Tokenizer | None) -> Tokenizer:
94
+ if tokenizer is None:
95
+ return _default_tokenizer()
96
+
97
+ if isinstance(tokenizer, str):
98
+ if importlib.util.find_spec("tiktoken"):
99
+ import tiktoken
100
+
101
+ try:
102
+ # Check if valid tiktoken encoding/model
103
+ try:
104
+ tiktoken.get_encoding(tokenizer)
105
+ return TiktokenTokenizer(tokenizer)
106
+ except ValueError:
107
+ try:
108
+ tiktoken.encoding_for_model(tokenizer)
109
+ return TiktokenTokenizer(tokenizer)
110
+ except ValueError:
111
+ pass
112
+ except ImportError:
113
+ pass
114
+
115
+ if importlib.util.find_spec("tokenizers"):
116
+ from tokenizers import Tokenizer
117
+
118
+ try:
119
+ return HuggingFaceTokenizerWrapper(Tokenizer.from_pretrained(tokenizer))
120
+ except Exception:
121
+ pass
122
+
123
+ raise ValueError(f"Could not resolve tokenizer: {tokenizer}")
124
+
125
+ # Check if it is a HuggingFace tokenizer object
126
+ if importlib.util.find_spec("tokenizers"):
127
+ from tokenizers import Tokenizer as HFTokenizer
128
+
129
+ if isinstance(tokenizer, HFTokenizer):
130
+ return HuggingFaceTokenizerWrapper(tokenizer)
131
+
132
+ return tokenizer
133
+
134
+
135
+ def _default_tokenizer() -> Tokenizer:
136
+ """Select a modern, lightweight tokenizer with graceful fallbacks."""
137
+ if importlib.util.find_spec("tiktoken"):
138
+ import tiktoken
139
+
140
+ for encoding in DEFAULT_TIKTOKEN_ENCODINGS:
141
+ try:
142
+ tiktoken.get_encoding(encoding)
143
+ return TiktokenTokenizer(encoding)
144
+ except ValueError:
145
+ continue
146
+
147
+ return WhitespaceTokenizer()
148
+
149
+
150
+ __all__ = [
151
+ "DEFAULT_TIKTOKEN_ENCODINGS",
152
+ "HuggingFaceTokenizerWrapper",
153
+ "TiktokenTokenizer",
154
+ "Tokenizer",
155
+ "WhitespaceTokenizer",
156
+ "resolve_tokenizer",
157
+ ]
glitchlings/auggie.py ADDED
@@ -0,0 +1,283 @@
1
+ """Laboratory assistant for composing gaggles with behaviour-focused helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable, Sequence
6
+ from typing import Collection, Literal
7
+
8
+ from .zoo.core import Gaggle, Glitchling
9
+ from .zoo.ekkokin import Ekkokin
10
+ from .zoo.hokey import Hokey
11
+ from .zoo.jargoyle import (
12
+ DEFAULT_LEXEMES,
13
+ DEFAULT_MODE,
14
+ Jargoyle,
15
+ JargoyleMode,
16
+ )
17
+ from .zoo.mim1c import Mim1c
18
+ from .zoo.pedant import Pedant
19
+ from .zoo.pedant.stones import PedantStone
20
+ from .zoo.redactyl import FULL_BLOCK, Redactyl
21
+ from .zoo.rushmore import Rushmore, RushmoreMode
22
+ from .zoo.scannequin import Scannequin
23
+ from .zoo.typogre import Typogre
24
+ from .zoo.zeedub import Zeedub
25
+
26
+
27
+ class Auggie(Gaggle):
28
+ """Assistant that incrementally assembles glitchlings into a gaggle."""
29
+
30
+ def __init__(
31
+ self,
32
+ glitchlings: Iterable[Glitchling] | None = None,
33
+ *,
34
+ seed: int = 151,
35
+ ) -> None:
36
+ self._blueprint: list[Glitchling] = []
37
+ initial = list(glitchlings or [])
38
+ super().__init__(initial, seed=seed)
39
+ if initial:
40
+ self._blueprint = [glitchling.clone() for glitchling in initial]
41
+ self._rebuild_plan()
42
+ else:
43
+ self._blueprint = []
44
+
45
+ def _rebuild_plan(self) -> None:
46
+ self._clones_by_index = []
47
+ for index, glitchling in enumerate(self._blueprint):
48
+ clone = glitchling.clone()
49
+ setattr(clone, "_gaggle_index", index)
50
+ self._clones_by_index.append(clone)
51
+ self.sort_glitchlings()
52
+
53
+ def _enqueue(self, glitchling: Glitchling) -> "Auggie":
54
+ self._blueprint.append(glitchling)
55
+ self._rebuild_plan()
56
+ return self
57
+
58
+ def clone(self, seed: int | None = None) -> "Auggie":
59
+ clone_seed = seed if seed is not None else self.seed
60
+ resolved_seed = 151 if clone_seed is None else int(clone_seed)
61
+ blueprint = [glitch.clone() for glitch in self._blueprint]
62
+ return Auggie(blueprint, seed=resolved_seed)
63
+
64
+ def typo(
65
+ self,
66
+ *,
67
+ rate: float | None = None,
68
+ keyboard: str = "CURATOR_QWERTY",
69
+ seed: int | None = None,
70
+ ) -> "Auggie":
71
+ """Add :class:`Typogre` using behaviour-driven nomenclature."""
72
+
73
+ return self._enqueue(Typogre(rate=rate, keyboard=keyboard, seed=seed))
74
+
75
+ def confusable(
76
+ self,
77
+ *,
78
+ rate: float | None = None,
79
+ classes: list[str] | Literal["all"] | None = None,
80
+ banned_characters: Collection[str] | None = None,
81
+ seed: int | None = None,
82
+ ) -> "Auggie":
83
+ """Add :class:`Mim1c` for homoglyph substitutions."""
84
+
85
+ return self._enqueue(
86
+ Mim1c(
87
+ rate=rate,
88
+ classes=classes,
89
+ banned_characters=banned_characters,
90
+ seed=seed,
91
+ )
92
+ )
93
+
94
+ def curly_quotes(self, *, seed: int | None = None) -> "Auggie":
95
+ """Add :class:`Pedant` evolved with Curlite to smarten punctuation."""
96
+
97
+ return self._enqueue(Pedant(stone=PedantStone.CURLITE, seed=seed))
98
+
99
+ def stretch(
100
+ self,
101
+ *,
102
+ rate: float = 0.3,
103
+ extension_min: int = 2,
104
+ extension_max: int = 5,
105
+ word_length_threshold: int = 6,
106
+ base_p: float = 0.45,
107
+ seed: int | None = None,
108
+ ) -> "Auggie":
109
+ """Add :class:`Hokey` for elongated, expressive words."""
110
+
111
+ return self._enqueue(
112
+ Hokey(
113
+ rate=rate,
114
+ extension_min=extension_min,
115
+ extension_max=extension_max,
116
+ word_length_threshold=word_length_threshold,
117
+ base_p=base_p,
118
+ seed=seed,
119
+ )
120
+ )
121
+
122
+ def homophone(
123
+ self,
124
+ *,
125
+ rate: float | None = None,
126
+ seed: int | None = None,
127
+ ) -> "Auggie":
128
+ """Add :class:`Ekkokin` to swap words for homophones."""
129
+
130
+ return self._enqueue(Ekkokin(rate=rate, seed=seed))
131
+
132
+ def pedantry(
133
+ self,
134
+ *,
135
+ stone: PedantStone | str = PedantStone.COEURITE,
136
+ seed: int | None = None,
137
+ ) -> "Auggie":
138
+ """Add :class:`Pedant` to evolve text via a chosen stone."""
139
+
140
+ return self._enqueue(Pedant(stone=stone, seed=seed))
141
+
142
+ def remix(
143
+ self,
144
+ *,
145
+ modes: RushmoreMode | str | Iterable[RushmoreMode | str] | None = None,
146
+ rate: float | None = None,
147
+ delete_rate: float | None = None,
148
+ duplicate_rate: float | None = None,
149
+ swap_rate: float | None = None,
150
+ seed: int | None = None,
151
+ unweighted: bool = False,
152
+ delete_unweighted: bool | None = None,
153
+ duplicate_unweighted: bool | None = None,
154
+ ) -> "Auggie":
155
+ """Add :class:`Rushmore` for deletion, duplication, and swap attacks."""
156
+
157
+ return self._enqueue(
158
+ Rushmore(
159
+ modes=modes,
160
+ rate=rate,
161
+ delete_rate=delete_rate,
162
+ duplicate_rate=duplicate_rate,
163
+ swap_rate=swap_rate,
164
+ seed=seed,
165
+ unweighted=unweighted,
166
+ delete_unweighted=delete_unweighted,
167
+ duplicate_unweighted=duplicate_unweighted,
168
+ )
169
+ )
170
+
171
+ def redact(
172
+ self,
173
+ *,
174
+ replacement_char: str = FULL_BLOCK,
175
+ rate: float | None = None,
176
+ merge_adjacent: bool = False,
177
+ seed: int | None = 151,
178
+ unweighted: bool = False,
179
+ ) -> "Auggie":
180
+ """Add :class:`Redactyl` to blackout words."""
181
+
182
+ return self._enqueue(
183
+ Redactyl(
184
+ replacement_char=replacement_char,
185
+ rate=rate,
186
+ merge_adjacent=merge_adjacent,
187
+ seed=seed if seed is not None else 151,
188
+ unweighted=unweighted,
189
+ )
190
+ )
191
+
192
+ def recolor(self, *, mode: JargoyleMode = "literal", seed: int | None = None) -> "Auggie":
193
+ """Add :class:`Jargoyle` with ``lexemes="colors"`` to remap colour terms.
194
+
195
+ Args:
196
+ mode: "literal" for deterministic first-entry swaps,
197
+ "drift" for random selection from palette.
198
+ seed: Seed for deterministic randomness.
199
+
200
+ Returns:
201
+ Self for method chaining.
202
+ """
203
+ return self._enqueue(Jargoyle(lexemes="colors", mode=mode, rate=1.0, seed=seed))
204
+
205
+ def drift(
206
+ self,
207
+ *,
208
+ lexemes: str = DEFAULT_LEXEMES,
209
+ mode: JargoyleMode = DEFAULT_MODE,
210
+ rate: float | None = None,
211
+ seed: int | None = None,
212
+ ) -> "Auggie":
213
+ """Add :class:`Jargoyle` for dictionary-based word drift.
214
+
215
+ Swaps words with alternatives from the specified lexeme dictionary.
216
+
217
+ Args:
218
+ lexemes: Dictionary to use. One of:
219
+ "colors" (color term swapping),
220
+ "synonyms" (general synonyms),
221
+ "corporate" (business jargon),
222
+ "academic" (scholarly terms).
223
+ mode: "literal" for deterministic first-entry swaps,
224
+ "drift" for random selection.
225
+ rate: Probability of transforming each matching word.
226
+ seed: Seed for deterministic randomness.
227
+
228
+ Returns:
229
+ Self for method chaining.
230
+ """
231
+ return self._enqueue(Jargoyle(lexemes=lexemes, mode=mode, rate=rate, seed=seed))
232
+
233
+ def ocr(
234
+ self,
235
+ *,
236
+ rate: float | None = None,
237
+ seed: int | None = None,
238
+ ) -> "Auggie":
239
+ """Add :class:`Scannequin` to simulate OCR artefacts."""
240
+
241
+ return self._enqueue(Scannequin(rate=rate, seed=seed))
242
+
243
+ def zero_width(
244
+ self,
245
+ *,
246
+ rate: float | None = None,
247
+ seed: int | None = None,
248
+ characters: Sequence[str] | None = None,
249
+ ) -> "Auggie":
250
+ """Add :class:`Zeedub` to hide zero-width glyphs inside text."""
251
+
252
+ return self._enqueue(Zeedub(rate=rate, seed=seed, characters=characters))
253
+
254
+ def synonym(
255
+ self,
256
+ *,
257
+ rate: float | None = None,
258
+ seed: int | None = None,
259
+ lexemes: str = "synonyms",
260
+ mode: JargoyleMode = "drift",
261
+ ) -> "Auggie":
262
+ """Add :class:`Jargoyle` for synonym substitutions.
263
+
264
+ Args:
265
+ rate: Probability of transforming each matching word.
266
+ seed: Seed for deterministic randomness.
267
+ lexemes: Dictionary to use (default "synonyms").
268
+ mode: "literal" or "drift" (default "drift").
269
+
270
+ Returns:
271
+ Self for method chaining.
272
+ """
273
+ return self._enqueue(
274
+ Jargoyle(
275
+ rate=rate,
276
+ seed=seed,
277
+ lexemes=lexemes,
278
+ mode=mode,
279
+ )
280
+ )
281
+
282
+
283
+ __all__ = ["Auggie"]
@@ -0,0 +1,9 @@
1
+ """Compatibility helpers centralising optional dependency imports and extras.
2
+
3
+ For 1.0, this package no longer re-exports loader utilities or type sentinels.
4
+ Import directly from ``glitchlings.compat.loaders`` or ``glitchlings.compat.types``.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ __all__: list[str] = []