codesuture 0.5.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,330 @@
1
+ import os
2
+ import marshal
3
+ import sys
4
+ import importlib.abc
5
+ import inspect
6
+ import json
7
+ from datetime import datetime
8
+ import threading
9
+
10
+ CACHE_DIR = ".codesuture_store"
11
+ HEALED_FUNCTIONS = set()
12
+ ANNOUNCED_HEALED_FUNCTIONS = set()
13
+ _store_lock = threading.Lock()
14
+
15
+ def _heal_key(module_name, func_name, key_name=None):
16
+
17
+ base = f"{module_name}.{func_name}"
18
+ if key_name:
19
+ return f"{base}:{key_name}"
20
+ return base
21
+
22
+ def save_patch(func, new_code, spec=None, ttl_days=7):
23
+ module_name = getattr(func, '__module__', None)
24
+ if not module_name:
25
+ return
26
+ func_name = getattr(func, '__qualname__', func.__name__)
27
+ func_name = func_name.replace('<', '_').replace('>', '_')
28
+
29
+ os.makedirs(CACHE_DIR, exist_ok=True)
30
+
31
+ base_name = f"{module_name}.{func_name}"
32
+ cache_path = os.path.join(CACHE_DIR, f"{base_name}.code")
33
+ json_path = os.path.join(CACHE_DIR, f"{base_name}.json")
34
+
35
+ with _store_lock:
36
+ with open(cache_path, "wb") as f:
37
+ marshal.dump(new_code, f)
38
+
39
+ if spec is not None:
40
+ target_name = spec.var_name
41
+ if spec.strategy == 'null_guard' and spec.key_name:
42
+ target_name = spec.key_name[-1] if isinstance(spec.key_name, tuple) else spec.key_name
43
+
44
+ metadata = {
45
+ "func_name": func_name,
46
+ "guard_type": spec.strategy,
47
+ "target": target_name,
48
+ "default_value": spec.default_value,
49
+ "patched_at": datetime.utcnow().isoformat(),
50
+ "ttl_days": ttl_days
51
+ }
52
+ metadata["thread"] = "MainThread"
53
+ with _store_lock:
54
+ with open(json_path, "w", encoding="utf-8") as f:
55
+ json.dump(metadata, f, indent=2)
56
+
57
+ def _announce_healed(module_name, func_name, key_name=None):
58
+ key = _heal_key(module_name, func_name, key_name)
59
+ if key in ANNOUNCED_HEALED_FUNCTIONS:
60
+ return
61
+ ANNOUNCED_HEALED_FUNCTIONS.add(key)
62
+ if key_name:
63
+ print(f"[CodeSuture] Already healed, skipping: loaded persistent patch for {module_name}.{func_name} ({key_name})")
64
+ else:
65
+ print(f"[CodeSuture] Already healed, skipping: loaded persistent patch for {module_name}.{func_name}")
66
+
67
+ def _load_cached_code(module_name, func_name):
68
+ func_name = func_name.replace('<', '_').replace('>', '_')
69
+ if not os.path.isdir(CACHE_DIR):
70
+ return None
71
+
72
+ base_name = f"{module_name}.{func_name}"
73
+ code_path = os.path.join(CACHE_DIR, f"{base_name}.code")
74
+ json_path = os.path.join(CACHE_DIR, f"{base_name}.json")
75
+ if not os.path.isfile(code_path):
76
+ return None
77
+
78
+ if os.path.isfile(json_path):
79
+ try:
80
+ with open(json_path, "r", encoding="utf-8") as f:
81
+ patch_data = json.load(f)
82
+ patched_at = datetime.fromisoformat(patch_data["patched_at"])
83
+ ttl_days = patch_data.get("ttl_days", 7)
84
+ age_days = (datetime.utcnow() - patched_at).days
85
+ if age_days > ttl_days:
86
+ print(
87
+ f"[CodeSuture] [WARN] Patch for '{patch_data.get('func_name', func_name)}' "
88
+ f"is {age_days} day(s) old (TTL={ttl_days}d). "
89
+ f"Verify root cause is fixed in source. "
90
+ f"Run 'codesuture audit' to review all patches."
91
+ )
92
+ except Exception:
93
+ pass
94
+
95
+ with open(code_path, "rb") as f:
96
+ return marshal.load(f)
97
+
98
+ def _load_learned_code(func, func_name):
99
+ from codesuture.knowledge import load_learned_rules
100
+
101
+ for rule in load_learned_rules():
102
+ if rule["func_name"] != func_name:
103
+ continue
104
+
105
+ from codesuture.guard_synthesizer import synthesize_guarded_code
106
+ from codesuture.pattern_matcher import PatchSpec
107
+
108
+ spec = PatchSpec(
109
+ strategy='autonomous_rule',
110
+ var_name=func_name,
111
+ default_value=rule["new_source"],
112
+ )
113
+ new_bc = synthesize_guarded_code(func.__code__, spec)
114
+ return new_bc.to_code()
115
+ return None
116
+
117
+ def apply_persisted_patch_to_function(func, module_name=None, func_name=None, key_name=None, announce=True):
118
+
119
+ module_name = module_name or getattr(func, '__module__', None)
120
+ func_name = func_name or getattr(func, '__qualname__', getattr(func, '__name__', None))
121
+ if not module_name or not func_name:
122
+ return False
123
+
124
+ new_code = _load_cached_code(module_name, func_name)
125
+ if new_code is None:
126
+ try:
127
+ new_code = _load_learned_code(func, func_name)
128
+ except Exception:
129
+ new_code = None
130
+ if new_code is None:
131
+ return False
132
+
133
+ from codesuture.code_replacer import replace_function_code
134
+
135
+ replace_function_code(func, new_code)
136
+ HEALED_FUNCTIONS.add(_heal_key(module_name, func_name, key_name))
137
+ if announce:
138
+ _announce_healed(module_name, func_name, key_name)
139
+ return True
140
+
141
+ def _iter_cached_function_names(module_name):
142
+ if not os.path.isdir(CACHE_DIR):
143
+ return
144
+ prefix = f"{module_name}."
145
+ for filename in os.listdir(CACHE_DIR):
146
+ if filename.startswith(prefix) and filename.endswith(".code"):
147
+ yield filename[len(prefix):-5]
148
+
149
+ def _resolve_attr(root, dotted_name):
150
+ obj = root
151
+ for part in dotted_name.split('.'):
152
+ obj = getattr(obj, part)
153
+ return obj
154
+
155
+ def apply_persisted_patch_to_value(module_name, binding_name, value):
156
+ if hasattr(value, '__wrapped__'):
157
+ value = getattr(value, '__wrapped__')
158
+ if inspect.isfunction(value):
159
+ return apply_persisted_patch_to_function(value, module_name=module_name)
160
+
161
+ applied = False
162
+ if inspect.isclass(value):
163
+ prefix = f"{binding_name}."
164
+ for func_name in _iter_cached_function_names(module_name) or ():
165
+ if not func_name.startswith(prefix):
166
+ continue
167
+ try:
168
+ target = _resolve_attr(value, func_name[len(prefix):])
169
+ if isinstance(target, property) and target.fget is not None:
170
+ target = target.fget
171
+ if inspect.ismethod(target):
172
+ target = target.__func__
173
+ if hasattr(target, '__wrapped__'):
174
+ target = getattr(target, '__wrapped__')
175
+ if inspect.isfunction(target):
176
+ applied = apply_persisted_patch_to_function(
177
+ target,
178
+ module_name=module_name,
179
+ func_name=func_name,
180
+ ) or applied
181
+ except Exception:
182
+ continue
183
+ return applied
184
+
185
+ class CodeSutureGlobals(dict):
186
+ def __init__(self, module_name, initial=None):
187
+ super().__init__()
188
+ self._codesuture_module_name = module_name
189
+ if initial:
190
+ for key, value in initial.items():
191
+ dict.__setitem__(self, key, value)
192
+
193
+ def __setitem__(self, key, value):
194
+ dict.__setitem__(self, key, value)
195
+ tracer = sys.gettrace()
196
+ sys.settrace(None)
197
+ try:
198
+ apply_persisted_patch_to_value(self._codesuture_module_name, key, value)
199
+ except Exception:
200
+ pass
201
+ finally:
202
+ sys.settrace(tracer)
203
+
204
+ def make_persisted_patch_globals(module_name, initial=None):
205
+ return CodeSutureGlobals(module_name, initial)
206
+
207
+ def apply_persisted_patches(module):
208
+ module_name = getattr(module, '__name__', None)
209
+ if not module_name:
210
+ return
211
+
212
+ for func_name in _iter_cached_function_names(module_name) or ():
213
+ try:
214
+ obj = _resolve_attr(module, func_name)
215
+ if isinstance(obj, property) and obj.fget is not None:
216
+ obj = obj.fget
217
+ if inspect.ismethod(obj):
218
+ obj = obj.__func__
219
+ if hasattr(obj, '__wrapped__'):
220
+ obj = getattr(obj, '__wrapped__')
221
+ if inspect.isfunction(obj):
222
+ apply_persisted_patch_to_function(obj, module_name, func_name)
223
+ except Exception:
224
+ pass
225
+
226
+ from codesuture.knowledge import load_learned_rules
227
+ rules = load_learned_rules()
228
+ for rule in rules:
229
+ func_name = rule["func_name"]
230
+
231
+ parts = func_name.split('.')
232
+ obj = module
233
+ try:
234
+ for part in parts:
235
+ obj = getattr(obj, part)
236
+
237
+ if _heal_key(module_name, func_name) in HEALED_FUNCTIONS:
238
+ continue
239
+
240
+ if isinstance(obj, property) and obj.fget is not None:
241
+ obj = obj.fget
242
+ if inspect.ismethod(obj):
243
+ obj = obj.__func__
244
+ if hasattr(obj, '__wrapped__'):
245
+ obj = getattr(obj, '__wrapped__')
246
+ if inspect.isfunction(obj):
247
+ apply_persisted_patch_to_function(obj, module_name, func_name)
248
+ except Exception:
249
+ continue
250
+
251
+ class CodeSutureLoaderWrapper(importlib.abc.Loader):
252
+ def __init__(self, loader):
253
+ self.loader = loader
254
+
255
+ def create_module(self, spec):
256
+ if hasattr(self.loader, 'create_module'):
257
+ return self.loader.create_module(spec)
258
+ return None
259
+
260
+ def exec_module(self, module):
261
+ self.loader.exec_module(module)
262
+ apply_persisted_patches(module)
263
+
264
+ def __getattr__(self, name):
265
+ return getattr(self.loader, name)
266
+
267
+ class CodeSutureMetaFinder(importlib.abc.MetaPathFinder):
268
+ def find_spec(self, fullname, path, target=None):
269
+ if hasattr(self, '_inside'): return None
270
+ self._inside = True
271
+ try:
272
+ for finder in sys.meta_path:
273
+ if finder is self: continue
274
+ if hasattr(finder, 'find_spec'):
275
+ spec = finder.find_spec(fullname, path, target)
276
+ if spec is not None and getattr(spec, 'loader', None) is not None:
277
+ if not isinstance(spec.loader, CodeSutureLoaderWrapper):
278
+ spec.loader = CodeSutureLoaderWrapper(spec.loader)
279
+ return spec
280
+ finally:
281
+ delattr(self, '_inside')
282
+ return None
283
+
284
+ def install_import_hook():
285
+ if not any(isinstance(f, CodeSutureMetaFinder) for f in sys.meta_path):
286
+ sys.meta_path.insert(0, CodeSutureMetaFinder())
287
+
288
+ for module_name, module in list(sys.modules.items()):
289
+ if module is not None:
290
+ apply_persisted_patches(module)
291
+
292
+ def patch_script_code(code_obj, module_name="__main__"):
293
+ if not os.path.isdir(CACHE_DIR):
294
+ return code_obj
295
+
296
+ new_consts = list(code_obj.co_consts)
297
+ changed = False
298
+ for i, const in enumerate(new_consts):
299
+ if type(const).__name__ == 'code':
300
+ func_name = const.co_name
301
+ base_name = f"{module_name}.{func_name}"
302
+ code_path = os.path.join(CACHE_DIR, f"{base_name}.code")
303
+ new_code = None
304
+ if os.path.isfile(code_path):
305
+ with open(code_path, "rb") as f:
306
+ new_code = marshal.load(f)
307
+ else:
308
+
309
+ from codesuture.knowledge import load_learned_rules
310
+ rules = load_learned_rules()
311
+ for rule in rules:
312
+ if rule["func_name"] == func_name:
313
+ from codesuture.guard_synthesizer import synthesize_guarded_code
314
+ from codesuture.pattern_matcher import PatchSpec
315
+ spec = PatchSpec(strategy='autonomous_rule', var_name=func_name, default_value=rule["new_source"])
316
+ try:
317
+ new_bc = synthesize_guarded_code(const, spec)
318
+ new_code = new_bc.to_code()
319
+ except Exception:
320
+ continue
321
+ break
322
+
323
+ if new_code:
324
+ new_consts[i] = new_code
325
+ changed = True
326
+ HEALED_FUNCTIONS.add(_heal_key(module_name, func_name))
327
+ _announce_healed(module_name, func_name)
328
+ if changed:
329
+ return code_obj.replace(co_consts=tuple(new_consts))
330
+ return code_obj
File without changes
@@ -0,0 +1,64 @@
1
+ import os
2
+ import re
3
+
4
+ _llm = None
5
+
6
+ def get_llm():
7
+ global _llm
8
+ if _llm is not None:
9
+ return _llm
10
+
11
+ from llama_cpp import Llama
12
+ model_path = os.environ.get("CODESUTURE_MODEL_PATH")
13
+ if not model_path or not os.path.exists(model_path):
14
+ raise FileNotFoundError(f"LLM model not found at {model_path}. Please download a model and set CODESUTURE_MODEL_PATH environment variable.")
15
+
16
+ print("[CodeSuture] Loading local LLM... this may take a moment.")
17
+ _llm = Llama(
18
+ model_path=model_path,
19
+ n_ctx=2048,
20
+ verbose=False
21
+ )
22
+ return _llm
23
+
24
+ def propose_fix(traceback_text, function_source, exc_type_name, exc_value):
25
+ llm = get_llm()
26
+
27
+ prompt = f"""<|system|>
28
+ You are an expert Python developer fixing bugs autonomously.
29
+ You are given the source code of a function that crashed, and the exception traceback.
30
+ Your task is to rewrite the ENTIRE function to safely handle the exception. You MUST modify the code inside the function to fix the error. The easiest way is to wrap the failing code in a try/except block and return a fallback value (like 0 or None), or to use an if-statement to check the inputs. Do NOT return the original crashing code. Do NOT output code outside of the function.
31
+ </s>
32
+ <|user|>
33
+ The function crashed with this error:
34
+ {exc_type_name}: {exc_value}
35
+
36
+ Original Source Code:
37
+ ```python
38
+ {function_source}
39
+ ```
40
+
41
+ Rewrite the ENTIRE function to fix this error. Output ONLY the valid python code block.
42
+ </s>
43
+ <|assistant|>
44
+ ```python
45
+ """
46
+
47
+ print("[CodeSuture] Asking LLM for a fix...")
48
+ response = llm(
49
+ prompt,
50
+ max_tokens=512,
51
+ stop=["```\n", "</s>"],
52
+ temperature=0.2
53
+ )
54
+
55
+ output = response['choices'][0]['text']
56
+
57
+ if "```python" in output:
58
+ output = output.split("```python")[1]
59
+ if "```" in output:
60
+ output = output.split("```")[0]
61
+
62
+ output = output.strip()
63
+ print(f"\n[CodeSuture] LLM Proposed Fix:\n{output}\n")
64
+ return output
codesuture/rewind.py ADDED
@@ -0,0 +1,43 @@
1
+ """
2
+ codesuture/rewind.py
3
+ Low‑level frame manipulation using ctypes.
4
+ """
5
+ import ctypes
6
+ import sys
7
+
8
+ class PyObject(ctypes.Structure):
9
+ _fields_ = [
10
+ ("ob_refcnt", ctypes.c_ssize_t),
11
+ ("ob_type", ctypes.c_void_p),
12
+ ]
13
+
14
+ class PyVarObject(PyObject):
15
+ _fields_ = [
16
+ ("ob_size", ctypes.c_ssize_t),
17
+ ]
18
+
19
+ class PyFrameObject(PyVarObject):
20
+
21
+ _fields_ = [
22
+ ("f_back", ctypes.c_void_p),
23
+ ("f_code", ctypes.c_void_p),
24
+ ("f_builtins", ctypes.c_void_p),
25
+ ("f_globals", ctypes.c_void_p),
26
+ ("f_locals", ctypes.c_void_p),
27
+ ("f_valuestack", ctypes.c_void_p),
28
+ ("f_stacktop", ctypes.c_void_p),
29
+ ("f_lasti", ctypes.c_int),
30
+ ("f_lineno", ctypes.c_int),
31
+
32
+ ]
33
+
34
+ def _cast_frame(frame):
35
+
36
+ return ctypes.cast(id(frame), ctypes.POINTER(PyFrameObject))
37
+
38
+ def rewind_frame_to_start(frame, code):
39
+
40
+ cf = _cast_frame(frame)
41
+
42
+ cf.contents.f_lasti = -1
43
+ cf.contents.f_lineno = code.co_firstlineno
codesuture/rollback.py ADDED
@@ -0,0 +1,85 @@
1
+
2
+ import os
3
+ import json
4
+ import shutil
5
+ from datetime import datetime
6
+
7
+ from codesuture.persistence import CACHE_DIR
8
+
9
+ def rollback_function(name):
10
+
11
+ if not os.path.isdir(CACHE_DIR):
12
+ print("[CodeSuture] Nothing to roll back.")
13
+ return
14
+
15
+ removed = 0
16
+ for fname in list(os.listdir(CACHE_DIR)):
17
+
18
+ base = fname.rsplit(".", 1)[0]
19
+ func_part = base.split(".", 1)[-1] if "." in base else base
20
+
21
+ if func_part == name or base == name or func_part.endswith(name):
22
+ path = os.path.join(CACHE_DIR, fname)
23
+ os.remove(path)
24
+ removed += 1
25
+
26
+ if removed > 0:
27
+ print(f"[CodeSuture] Rolled back patch for '{name}'. "
28
+ f"Run your script again to re-patch if needed.")
29
+ else:
30
+ print(f"[CodeSuture] No patch found matching '{name}'.")
31
+
32
+ def rollback_all():
33
+
34
+ count = 0
35
+ if os.path.isdir(CACHE_DIR):
36
+ count = len(os.listdir(CACHE_DIR))
37
+ if count == 0:
38
+ print("[CodeSuture] Nothing to roll back.")
39
+ return
40
+ shutil.rmtree(CACHE_DIR)
41
+ else:
42
+ print("[CodeSuture] Nothing to roll back.")
43
+ return
44
+
45
+ fp = ".codesuture_fingerprints"
46
+ if os.path.isfile(fp):
47
+ os.remove(fp)
48
+
49
+ print(f"[CodeSuture] Cleared {count} patch file(s) and fingerprint registry.")
50
+
51
+ def rollback_dry_run():
52
+
53
+ if not os.path.isdir(CACHE_DIR):
54
+ print("[CodeSuture] Nothing to roll back. Store does not exist.")
55
+ return
56
+
57
+ json_files = [f for f in os.listdir(CACHE_DIR) if f.endswith(".json")]
58
+ if not json_files:
59
+ print("[CodeSuture] Nothing to roll back. No patches found.")
60
+ return
61
+
62
+ now = datetime.utcnow()
63
+ print()
64
+ print(" [CodeSuture DRY-RUN] Would remove the following patches:")
65
+ print()
66
+ for jf in json_files:
67
+ path = os.path.join(CACHE_DIR, jf)
68
+ try:
69
+ with open(path, "r", encoding="utf-8") as f:
70
+ data = json.load(f)
71
+ func = data.get("func_name", "?")
72
+ guard = data.get("guard_type", "?")
73
+ age = "?"
74
+ if "patched_at" in data:
75
+ dt = datetime.fromisoformat(data["patched_at"])
76
+ age = f"{(now - dt).days}d"
77
+ print(f" - {func} guard={guard} age={age}")
78
+ except Exception:
79
+ print(f" - {jf} (could not read metadata)")
80
+
81
+ fp = ".codesuture_fingerprints"
82
+ if os.path.isfile(fp):
83
+ print(f" - .codesuture_fingerprints (fingerprint registry)")
84
+ print()
85
+ print(" Run 'codesuture rollback --all' to actually remove them.")
codesuture/sandbox.py ADDED
@@ -0,0 +1,105 @@
1
+ import subprocess
2
+ import sys
3
+ import os
4
+ import tempfile
5
+
6
+ def test_fix(original_script_path, module_name, func_name, new_source, exc_type_name):
7
+ print(f"[CodeSuture Sandbox] Testing fix for {module_name}.{func_name}...")
8
+
9
+ with tempfile.NamedTemporaryFile("w", delete=False, suffix=".py", encoding='utf-8') as f:
10
+ f.write(new_source)
11
+ source_path = f.name
12
+
13
+ with tempfile.NamedTemporaryFile("w", delete=False, suffix=".py", encoding='utf-8') as f:
14
+ runner_code = f"""
15
+ import sys
16
+ import importlib
17
+
18
+ def main():
19
+ with open({repr(source_path)}, 'r', encoding='utf-8') as f:
20
+ new_source = f.read()
21
+
22
+ new_module_code = compile(new_source, "<sandbox>", 'exec')
23
+ new_func_code = None
24
+ for const in new_module_code.co_consts:
25
+ if type(const).__name__ == 'code' and const.co_name == {repr(func_name)}:
26
+ new_func_code = const
27
+ break
28
+
29
+ if not new_func_code:
30
+ print("SANDBOX ERROR: Could not find function in compiled new source")
31
+ sys.exit(1)
32
+
33
+ if {repr(module_name)} != '__main__':
34
+ from codesuture.code_replacer import replace_function_code
35
+ mod = importlib.import_module({repr(module_name)})
36
+ # Find the function object inside the module
37
+ parts = {repr(func_name)}.split('.')
38
+ obj = mod
39
+ for part in parts:
40
+ obj = getattr(obj, part)
41
+ replace_function_code(obj, new_func_code)
42
+
43
+ # Now run the script
44
+ with open({repr(original_script_path)}, 'r', encoding='utf-8') as script_file:
45
+ source = script_file.read()
46
+ code = compile(source, {repr(original_script_path)}, 'exec')
47
+ globs = {{'__name__': '__main__', '__file__': {repr(original_script_path)}}}
48
+ try:
49
+ exec(code, globs)
50
+ except Exception as e:
51
+ print(f"SANDBOX EXCEPTION: {{type(e).__name__}}")
52
+ sys.exit(1)
53
+ else:
54
+ # Patching __main__ script
55
+ with open({repr(original_script_path)}, 'r', encoding='utf-8') as script_file:
56
+ source = script_file.read()
57
+ code = compile(source, {repr(original_script_path)}, 'exec')
58
+
59
+ new_consts = list(code.co_consts)
60
+ for i, const in enumerate(new_consts):
61
+ if type(const).__name__ == 'code' and const.co_name == {repr(func_name)}:
62
+ new_consts[i] = new_func_code
63
+ break
64
+ code = code.replace(co_consts=tuple(new_consts))
65
+
66
+ globs = {{'__name__': '__main__', '__file__': {repr(original_script_path)}}}
67
+ try:
68
+ exec(code, globs)
69
+ except Exception as e:
70
+ print(f"SANDBOX EXCEPTION: {{type(e).__name__}}")
71
+ sys.exit(1)
72
+
73
+ if __name__ == '__main__':
74
+ main()
75
+ """
76
+ f.write(runner_code)
77
+ runner_path = f.name
78
+
79
+ try:
80
+ result = subprocess.run(
81
+ [sys.executable, runner_path],
82
+ capture_output=True,
83
+ text=True,
84
+ timeout=3
85
+ )
86
+ output = result.stdout + result.stderr
87
+
88
+ if f"SANDBOX EXCEPTION: {exc_type_name}" in output:
89
+ print("[CodeSuture Sandbox] Fix FAILED: original exception still occurs.")
90
+ return False
91
+ elif "SANDBOX EXCEPTION" in output:
92
+ print(f"[CodeSuture Sandbox] Fix FAILED: caused a new exception.\n{output}")
93
+ return False
94
+ elif result.returncode != 0:
95
+ print(f"[CodeSuture Sandbox] Fix FAILED: subprocess exited with error.\n{output}")
96
+ return False
97
+
98
+ print("[CodeSuture Sandbox] Fix PASSED!")
99
+ return True
100
+ except subprocess.TimeoutExpired:
101
+ print("[CodeSuture Sandbox] Fix FAILED: Timeout (possible infinite loop).")
102
+ return False
103
+ finally:
104
+ os.remove(source_path)
105
+ os.remove(runner_path)
codesuture/shadow.py ADDED
@@ -0,0 +1,20 @@
1
+ SENTINEL_VALUES = {"", 0, 0.0, None, False, (), frozenset()}
2
+
3
+ def is_sentinel(value) -> bool:
4
+ try:
5
+ if value in SENTINEL_VALUES:
6
+ return True
7
+ if value == [] or value == {}:
8
+ return True
9
+ except Exception:
10
+ pass
11
+ return False
12
+
13
+ def shadow_check(func_name: str, return_value, guard_type: str):
14
+ if is_sentinel(return_value):
15
+ print(
16
+ f"[CodeSuture SHADOW] âš  {func_name}() returned sentinel "
17
+ f"value {return_value!r} after {guard_type} patch. "
18
+ f"Verify this default is safe for downstream consumers."
19
+ )
20
+