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.
codesuture/tracer.py ADDED
@@ -0,0 +1,447 @@
1
+ import sys
2
+ import os
3
+ import json
4
+ from datetime import datetime
5
+ from codesuture.pattern_matcher import analyze_exception
6
+ from codesuture.guard_synthesizer import synthesize_guarded_code
7
+ from codesuture.code_replacer import replace_function_code, get_function_from_frame
8
+ from codesuture.rewind import rewind_frame_to_start
9
+
10
+ _PYTHON_PREFIX = os.path.normcase(os.path.abspath(sys.prefix)) + os.sep
11
+ _PYTHON_BASE_PREFIX = os.path.normcase(os.path.abspath(sys.base_prefix)) + os.sep
12
+
13
+ def _is_internal_frame(frame):
14
+
15
+ co_filename = frame.f_code.co_filename
16
+
17
+ if co_filename.startswith('<'):
18
+ return True
19
+
20
+ try:
21
+ norm = os.path.normcase(os.path.abspath(co_filename))
22
+ if norm.startswith(_PYTHON_PREFIX) or norm.startswith(_PYTHON_BASE_PREFIX):
23
+ return True
24
+ except (ValueError, OSError):
25
+ pass
26
+ return False
27
+
28
+ class CodeSutureTracer:
29
+ def __init__(self, dry_run=False, log_file=None, max_retries=3, autonomous=False, script_path=None, verbose=False, shadow=False, ttl=7):
30
+ self.dry_run = dry_run
31
+ self.log_file = log_file
32
+ self.max_retries = max_retries
33
+ self.autonomous = autonomous
34
+ self.script_path = script_path
35
+ self.verbose = verbose
36
+ self.shadow_mode = shadow
37
+ self.ttl = ttl
38
+ self._patched_codes = {}
39
+ self.attempts = {}
40
+ self.stats = {
41
+ "patched": 0,
42
+ "dry_run_suggestions": 0,
43
+ "self_healed": 0
44
+ }
45
+ self.patched_signatures = {}
46
+ self._handled_exc_ids = set()
47
+
48
+ def __call__(self, frame, event, arg):
49
+ if event == 'return' and self.shadow_mode and frame.f_code in self._patched_codes:
50
+ from codesuture.shadow import shadow_check
51
+ func_name = frame.f_code.co_name
52
+ guard_type = self._patched_codes[frame.f_code]
53
+ shadow_check(func_name, arg, guard_type)
54
+ return self
55
+
56
+ if event == 'exception':
57
+ exc_type, exc_value, exc_tb = arg
58
+ self._handle_exception(frame, exc_type, exc_value, exc_tb)
59
+ return self
60
+ return self
61
+
62
+ def _extract_crash_key(self, exc_type, exc_value):
63
+
64
+ import re
65
+ if exc_type.__name__ == 'KeyError':
66
+ return str(exc_value).strip("'\"")
67
+ elif exc_type.__name__ == 'AttributeError':
68
+ m = re.search(r"has no attribute '(\w+)'", str(exc_value))
69
+ if m:
70
+ return m.group(1)
71
+ elif exc_type.__name__ == 'TypeError':
72
+ m = re.search(r"'NoneType' object is not subscriptable", str(exc_value))
73
+ if m:
74
+ return '__subscript__'
75
+ return None
76
+
77
+ def _handle_exception(self, frame, exc_type, exc_value, exc_tb, thread=None):
78
+
79
+ if _is_internal_frame(frame):
80
+ return
81
+
82
+ from codesuture.persistence import HEALED_FUNCTIONS, _heal_key
83
+ from codesuture.code_replacer import get_function_from_frame
84
+ try:
85
+ func = get_function_from_frame(frame)
86
+ if func is not None:
87
+ func_name = getattr(func, '__qualname__', func.__name__)
88
+ module_name = getattr(func, '__module__', '__main__')
89
+ crash_key = self._extract_crash_key(exc_type, exc_value)
90
+ if _heal_key(module_name, func_name, crash_key) in HEALED_FUNCTIONS:
91
+ return
92
+ except Exception:
93
+ pass
94
+
95
+ exc_id = id(exc_value)
96
+ if exc_id in self._handled_exc_ids:
97
+ return
98
+
99
+ spec = None
100
+ from codesuture.fingerprint import compute_fingerprint, lookup, record
101
+ fp = compute_fingerprint(frame.f_code, frame.f_lasti, exc_type.__name__)
102
+ cached = lookup(fp)
103
+ if cached:
104
+ print(f"[CodeSuture] Known crash pattern #{fp[:8]} -- "
105
+ f"applying cached {cached['guard_type']} guard directly.")
106
+ from codesuture.pattern_matcher import PatchSpec
107
+
108
+ spec = PatchSpec(
109
+ strategy=cached['guard_type'],
110
+ var_name=cached['target'],
111
+ default_value=cached.get('default_value', None),
112
+ key_name=tuple(cached.get('key_name')) if isinstance(cached.get('key_name'), list) else cached.get('key_name', None)
113
+ )
114
+
115
+ if spec is None:
116
+ try:
117
+ spec = analyze_exception(frame, exc_type, exc_value, exc_tb)
118
+ except Exception as internal_exc:
119
+
120
+ spec = self._self_heal(internal_exc)
121
+ if spec is None:
122
+ return
123
+
124
+ try:
125
+ spec = analyze_exception(frame, exc_type, exc_value, exc_tb)
126
+ except Exception:
127
+ return
128
+
129
+ if spec is None:
130
+
131
+ from codesuture.pattern_matcher import check_learned_rules
132
+ func = get_function_from_frame(frame)
133
+ if func is not None:
134
+ func_name = getattr(func, '__qualname__', func.__name__)
135
+ spec = check_learned_rules(func_name, exc_type.__name__, str(exc_value))
136
+
137
+ if spec is None and self.autonomous and func is not None:
138
+
139
+ print(f"[CodeSuture] Autonomous mode activated for unknown error: {exc_type.__name__}")
140
+ import traceback
141
+ from codesuture.code_replacer import get_source_from_frame
142
+ from codesuture.plugins.autonomous import propose_fix
143
+ from codesuture.sandbox import test_fix
144
+ from codesuture.knowledge import save_learned_rule
145
+ from codesuture.pattern_matcher import PatchSpec
146
+
147
+ tb_text = "".join(traceback.format_exception(exc_type, exc_value, exc_tb))
148
+ function_source = get_source_from_frame(frame)
149
+
150
+ new_source = propose_fix(tb_text, function_source, exc_type.__name__, str(exc_value))
151
+
152
+ module_name = getattr(func, '__module__', '__main__')
153
+
154
+ if test_fix(self.script_path, module_name, func_name, new_source, exc_type.__name__):
155
+ print(f"[CodeSuture] LLM fix PASSED sandbox. Learning rule for {func_name}.")
156
+ save_learned_rule(exc_type.__name__, str(exc_value), func_name, new_source)
157
+ spec = PatchSpec(
158
+ strategy='autonomous_rule',
159
+ var_name=func_name,
160
+ default_value=new_source
161
+ )
162
+ else:
163
+ print("[CodeSuture] LLM fix FAILED sandbox. Skipping autonomous patch.")
164
+
165
+ if spec is None:
166
+ return
167
+
168
+ key = (id(frame.f_code), frame.f_lasti)
169
+ tries = self.attempts.get(key, 0)
170
+ if tries >= self.max_retries:
171
+ print(f"[CodeSuture] Max retries ({self.max_retries}) reached at "
172
+ f"{frame.f_code.co_name}:{frame.f_lineno}, giving up.")
173
+ return
174
+
175
+ self.attempts[key] = tries + 1
176
+
177
+ _thread_name = thread.name if thread is not None else None
178
+
179
+ entry = {
180
+ "timestamp": datetime.now().isoformat(),
181
+ "function": frame.f_code.co_name,
182
+ "filename": frame.f_code.co_filename,
183
+ "lineno": frame.f_lineno,
184
+ "exception": f"{exc_type.__name__}: {exc_value}",
185
+ "strategy": spec.strategy,
186
+ "var_name": spec.var_name,
187
+ "default": repr(spec.default_value),
188
+ }
189
+ if _thread_name is not None:
190
+ entry["thread"] = _thread_name
191
+
192
+ display_name = spec.var_name
193
+ if spec.key_name:
194
+ display_name = spec.key_name[-1] if isinstance(spec.key_name, tuple) else spec.key_name
195
+ elif spec.strategy == 'null_guard' and exc_type.__name__ == 'AttributeError':
196
+ import re
197
+ m = re.search(r"has no attribute '(\w+)'", str(exc_value))
198
+ if m:
199
+ display_name = m.group(1)
200
+
201
+ if self.dry_run:
202
+ entry["action"] = "dry_run"
203
+ from codesuture.fingerprint import lookup as fp_lookup
204
+ fp_hit = fp_lookup(fp) if fp else None
205
+ if fp_hit:
206
+ try:
207
+ import os as _os
208
+ fp_file = ".codesuture_fingerprints"
209
+ if _os.path.isfile(fp_file):
210
+ with open(fp_file, "r", encoding="utf-8") as fpf:
211
+ fp_data = json.load(fpf)
212
+ count = fp_data.get(fp, {}).get("count", 1) if isinstance(fp_data.get(fp), dict) else 1
213
+ else:
214
+ count = 0
215
+ except Exception:
216
+ count = 0
217
+ else:
218
+ count = 0
219
+ if count >= 3:
220
+ confidence = "HIGH"
221
+ elif count >= 1:
222
+ confidence = "MEDIUM"
223
+ else:
224
+ confidence = "LOW"
225
+ confidence_detail = (f"pattern seen {count}x in fingerprint registry" if count > 0
226
+ else "new pattern, not in fingerprint registry")
227
+ print(f"[CodeSuture DRY-RUN] Would apply {spec.strategy} on '{display_name}' in {frame.f_code.co_name}()")
228
+ print(f"[CodeSuture DRY-RUN] Confidence: {confidence} ({confidence_detail})")
229
+ print(f" Default value: {repr(spec.default_value)}")
230
+ print(f" Guard type: {spec.strategy}")
231
+ self._log(entry)
232
+ self.stats["dry_run_suggestions"] += 1
233
+ return
234
+ else:
235
+ print(f"[CodeSuture] Caught {exc_type.__name__}: {exc_value}")
236
+
237
+ sig = (spec.var_name, spec.key_name, spec.strategy, exc_type.__name__)
238
+ is_reuse = sig in self.patched_signatures
239
+
240
+ if is_reuse:
241
+ print(f"[CodeSuture] Reusing existing patch for '{display_name}' in {frame.f_code.co_name}()")
242
+ spec = self.patched_signatures[sig]
243
+
244
+ if not cached:
245
+ print(f"[CodeSuture] Applying {spec.strategy} on '{display_name}' ...")
246
+
247
+ try:
248
+ if getattr(spec, 'target_func', None):
249
+ func = spec.target_func
250
+ old_code = getattr(func, '__code__', frame.f_code)
251
+ else:
252
+ func = get_function_from_frame(frame)
253
+ old_code = frame.f_code
254
+
255
+ new_bc = synthesize_guarded_code(old_code, spec)
256
+ new_code = new_bc.to_code()
257
+ self._persist_patch(frame, old_code, new_code, func)
258
+
259
+ replace_function_code(func, new_code)
260
+
261
+ if getattr(spec, 'target_func', None):
262
+ assert spec.target_func.__code__ is new_code, "Property fget code replacement failed"
263
+
264
+ from codesuture.persistence import save_patch
265
+ save_patch(func, new_code, spec, self.ttl)
266
+
267
+ if self.shadow_mode:
268
+ self._patched_codes[new_code] = spec.strategy
269
+
270
+ if self.verbose:
271
+ from codesuture.diff_guard import semantic_diff
272
+ diff = semantic_diff(old_code, new_code, spec.strategy)
273
+ print(f"[CodeSuture DEBUG] Diff: +{diff.added} -{diff.removed} instructions (allowed <= {diff.allowed})")
274
+
275
+ if not is_reuse:
276
+ self.patched_signatures[sig] = spec
277
+
278
+ if not cached:
279
+ record(fp, spec.strategy, spec.var_name, getattr(func, '__name__', 'unknown'), exc_type.__name__, spec.default_value, spec.key_name)
280
+
281
+ entry["action"] = "applied"
282
+ self._log(entry)
283
+ self.stats["patched"] += 1
284
+ self._handled_exc_ids.add(exc_id)
285
+ print(f"[CodeSuture] Patch applied to {getattr(func, '__name__', 'unknown')}().")
286
+ return
287
+ except Exception as e:
288
+ from codesuture.guard_synthesizer import PatchValidationError, PatchRejectedError
289
+ if isinstance(e, PatchValidationError):
290
+ print(f"[CodeSuture] {e}")
291
+ entry["action"] = "rejected"
292
+ elif isinstance(e, PatchRejectedError):
293
+ entry["action"] = "rejected"
294
+ elif isinstance(e, RuntimeError) and old_code.co_flags & 0x100:
295
+ print(f"[CodeSuture] WARNING: async patch for {old_code.co_name}() "
296
+ f"raised RuntimeError: {e} -- aborting patch, not persisting.")
297
+ entry["action"] = "aborted"
298
+ else:
299
+ import traceback as _tb
300
+ _tb.print_exc()
301
+ print(f"[CodeSuture] Patch failed: {e}")
302
+ entry["action"] = "failed"
303
+
304
+ entry["error"] = str(e)
305
+ self._log(entry)
306
+ return
307
+
308
+ def _self_heal(self, internal_exc):
309
+
310
+ import traceback as tb_mod
311
+ internal_tb = sys.exc_info()[2]
312
+ if internal_tb is None:
313
+ return None
314
+ curr = internal_tb
315
+ while curr.tb_next:
316
+ curr = curr.tb_next
317
+ internal_frame = curr.tb_frame
318
+
319
+ print(f"[CodeSuture] ENGINE SELF-HEAL: caught internal {type(internal_exc).__name__}: {internal_exc}")
320
+ print(f"[CodeSuture] in {internal_frame.f_code.co_name}() at {internal_frame.f_code.co_filename}:{internal_frame.f_lineno}")
321
+
322
+ try:
323
+ spec = analyze_exception(
324
+ internal_frame, type(internal_exc), internal_exc, internal_tb
325
+ )
326
+ except Exception:
327
+ print("[CodeSuture] self-heal analysis failed")
328
+ return None
329
+
330
+ if spec is None:
331
+ print("[CodeSuture] no deterministic patch found for internal error")
332
+ return None
333
+
334
+ print(f"[CodeSuture] Applying {spec.strategy} on '{spec.var_name}' …")
335
+ try:
336
+ func = get_function_from_frame(internal_frame)
337
+ new_bc = synthesize_guarded_code(internal_frame.f_code, spec)
338
+ new_code = new_bc.to_code()
339
+ replace_function_code(func, new_code)
340
+ self.stats["patched"] += 1
341
+ print(f"[CodeSuture] Self-healed {func.__name__}().")
342
+
343
+ from codesuture.persistence import save_patch
344
+ save_patch(func, new_code)
345
+
346
+ return spec
347
+ except Exception as e:
348
+ print(f"[CodeSuture] self-heal patch failed: {e}")
349
+ return None
350
+
351
+ def _persist_patch(self, frame, old_code, new_code, func=None):
352
+ import gc
353
+ import ctypes
354
+ replaced = False
355
+ propagated_count = 0
356
+
357
+ refs = gc.get_referrers(old_code)
358
+ for ref in refs:
359
+ if hasattr(ref, "__code__") and getattr(ref, "__code__", None) is old_code:
360
+ try:
361
+ ref.__code__ = new_code
362
+ replaced = True
363
+ propagated_count += 1
364
+ except Exception:
365
+ pass
366
+ elif hasattr(ref, "__func__"):
367
+ fn = getattr(ref, "__func__", None)
368
+ if hasattr(fn, "__code__") and getattr(fn, "__code__", None) is old_code:
369
+ try:
370
+ fn.__code__ = new_code
371
+ replaced = True
372
+ propagated_count += 1
373
+ except Exception:
374
+ pass
375
+ elif isinstance(ref, tuple):
376
+ for i, c in enumerate(ref):
377
+ if c is old_code:
378
+ try:
379
+ addr = id(ref) + 24 + i * 8
380
+ ctypes.c_void_p.from_address(addr).value = id(new_code)
381
+ ctypes.pythonapi.Py_IncRef(ctypes.py_object(new_code))
382
+ replaced = True
383
+ except Exception:
384
+ pass
385
+
386
+ if propagated_count > 0:
387
+ print(f"[CodeSuture] Propagated patch to {propagated_count} additional live reference(s) of {frame.f_code.co_name}.")
388
+ elif replaced:
389
+ print(f"[CodeSuture] In-memory propagated patch applied to {frame.f_code.co_name}.")
390
+ else:
391
+ if func is None:
392
+ func_name = frame.f_code.co_name
393
+ func = frame.f_globals.get(func_name)
394
+
395
+ if func and hasattr(func, "__code__") and getattr(func, "__code__", None) is old_code:
396
+ func.__code__ = new_code
397
+ print(f"[CodeSuture] In-memory propagated patch applied to {func.__name__}().")
398
+ else:
399
+ print("[CodeSuture] Could not find code object in memory to persist.")
400
+
401
+ def _log(self, entry):
402
+ if self.log_file:
403
+ with open(self.log_file, 'a', encoding='utf-8') as f:
404
+ json.dump(entry, f, default=str)
405
+ f.write('\n')
406
+
407
+ def report(self):
408
+ print("\n[CodeSuture] Session summary:")
409
+ print(f" Patches applied: {len(self.patched_signatures)}")
410
+ if self.dry_run:
411
+ print(f" Dry-run suggestions: {self.stats['dry_run_suggestions']}")
412
+ print(f"[CodeSuture DRY-RUN] No patches applied. Run without --dry-run to apply.")
413
+
414
+ _original_excepthook = None
415
+
416
+ def _codesuture_excepthook(tracer, exc_type, exc_value, exc_tb):
417
+ import threading
418
+ if exc_tb:
419
+ tracer._handle_exception(exc_tb.tb_frame, exc_type, exc_value, exc_tb, thread=threading.current_thread())
420
+
421
+ if _original_excepthook:
422
+ _original_excepthook(exc_type, exc_value, exc_tb)
423
+ else:
424
+ sys.__excepthook__(exc_type, exc_value, exc_tb)
425
+
426
+ def install(dry_run=False, log_file=None, max_retries=3, autonomous=False, script_path=None, verbose=False, shadow=False, ttl=7):
427
+ global _original_excepthook
428
+ import threading
429
+ tracer = CodeSutureTracer(dry_run, log_file, max_retries, autonomous, script_path, verbose, shadow, ttl)
430
+ sys.settrace(tracer)
431
+ threading.settrace(tracer)
432
+
433
+ if getattr(threading, 'excepthook', None) is not None:
434
+ if threading.excepthook != getattr(threading, '__excepthook__', None):
435
+ _original_excepthook = threading.excepthook
436
+ threading.excepthook = lambda args: _codesuture_excepthook(tracer, args.exc_type, args.exc_value, args.exc_traceback)
437
+
438
+ return tracer
439
+
440
+ def uninstall():
441
+ global _original_excepthook
442
+ sys.settrace(None)
443
+ import threading
444
+ threading.settrace(None)
445
+ if getattr(threading, 'excepthook', None) is not None:
446
+ threading.excepthook = _original_excepthook or getattr(threading, '__excepthook__', sys.__excepthook__)
447
+ _original_excepthook = None
codesuture/watcher.py ADDED
@@ -0,0 +1,78 @@
1
+
2
+ import subprocess
3
+ import sys
4
+ import time
5
+
6
+ def watch(script, max_restarts=10, shadow=False, verbose=False):
7
+
8
+ restarts = 0
9
+ last_exception = None
10
+ same_exception_count = 0
11
+
12
+ print(f"[CodeSuture WATCH] Starting watch on {script} (max-restarts={max_restarts})")
13
+
14
+ while restarts <= max_restarts:
15
+ cmd = [sys.executable, "-m", "codesuture", "run"]
16
+ if shadow:
17
+ cmd.append("--shadow")
18
+ if verbose:
19
+ cmd.append("--verbose")
20
+ cmd.append(script)
21
+
22
+ try:
23
+ result = subprocess.run(
24
+ cmd,
25
+ capture_output=True,
26
+ text=True,
27
+ encoding="utf-8",
28
+ errors="replace",
29
+ timeout=60,
30
+ )
31
+ except subprocess.TimeoutExpired:
32
+ print("[CodeSuture WATCH] Script timed out.")
33
+ restarts += 1
34
+ continue
35
+
36
+ output = (result.stdout or "") + (result.stderr or "")
37
+ print(output, end="")
38
+
39
+ if result.returncode == 0:
40
+ print("[CodeSuture WATCH] Script exited cleanly.")
41
+ return 0
42
+
43
+ patches_applied = output.lower().count("patch applied")
44
+
45
+ current_exception = _extract_exception(output)
46
+ if current_exception and current_exception == last_exception and patches_applied == 0:
47
+ same_exception_count += 1
48
+ else:
49
+ same_exception_count = 0
50
+ last_exception = current_exception
51
+
52
+ if same_exception_count >= 2:
53
+ print("[CodeSuture WATCH] Unrecoverable: same exception fired 3x with 0 new patches.")
54
+ return 1
55
+
56
+ restarts += 1
57
+
58
+ if restarts > max_restarts:
59
+ break
60
+
61
+ print(f"[CodeSuture WATCH] Restarting ({restarts}/{max_restarts})...")
62
+ time.sleep(0.5)
63
+
64
+ print(f"[CodeSuture WATCH] Max restarts ({max_restarts}) reached.")
65
+ return 1
66
+
67
+ def _extract_exception(output):
68
+
69
+ import re
70
+
71
+ matches = re.findall(r"(?:Caught |Script exited with: )(\w+Error[:\s].*?)(?:\n|$)", output)
72
+ if matches:
73
+ return matches[-1].strip()
74
+
75
+ matches = re.findall(r"(\w+Error: .+?)(?:\n|$)", output)
76
+ if matches:
77
+ return matches[-1].strip()
78
+ return None
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: codesuture
3
+ Version: 0.5.0
4
+ Summary: Runtime Python bytecode patcher with guard knowledge base, persistence, and self-healing re-execution
5
+ License-Expression: MIT
6
+ Project-URL: Source, https://github.com/codesuture-py/codesuture
7
+ Keywords: bytecode,runtime,patching,self-healing,debugging,null-safety,resilience
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Software Development :: Debuggers
11
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Python: >=3.11
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: bytecode>=0.15.1
18
+ Provides-Extra: autonomous
19
+ Requires-Dist: llama-cpp-python; extra == "autonomous"
20
+ Dynamic: license-file
21
+
22
+ # CodeSuture
23
+
24
+ > Runtime Python bytecode patcher. Catches crashes, synthesizes guards, rewrites functions in-memory, and persists fixes across runs.
25
+
26
+ ## What it does
27
+
28
+ CodeSuture intercepts runtime exceptions in your Python program, analyzes the failing bytecode to determine the root cause, synthesizes a deterministic guard (such as a null check or bounds clamp), rewrites the function's bytecode in memory, rewinds execution to retry, and persists the fix so it loads instantly on subsequent runs. No source files are modified. It is a surgical debugging tool that turns crashes into self-healing code.
29
+
30
+ ## Quick start
31
+
32
+ ```bash
33
+ pip install codesuture
34
+ ```
35
+
36
+ ```bash
37
+ codesuture run your_buggy_script.py
38
+ ```
39
+
40
+ ```
41
+ [CodeSuture] Caught AttributeError: 'NoneType' object has no attribute 'bio'
42
+ [CodeSuture] Applying null_guard on 'profile' ...
43
+ [CodeSuture] Patch applied to get_bio().
44
+ [CodeSuture] Re-executing after 1 patch(es)...
45
+
46
+ Session summary:
47
+ Patches applied: 1
48
+ ```
49
+
50
+ ## How it works
51
+
52
+ 1. **Catch** — A `sys.settrace` callback intercepts exceptions at the exact frame and instruction offset where they occur.
53
+ 2. **Analyze** — The pattern matcher disassembles the function's bytecode, identifies the failing variable/operation, and selects the appropriate guard type.
54
+ 3. **Patch** — The guard synthesizer injects new bytecode instructions (null checks, bounds clamps, safe `.get()` calls) into the function's code object. A semantic diff gate rejects patches that change too many instructions.
55
+ 4. **Rewind** — Execution restarts from the top of the patched function. The guard prevents the same crash from recurring.
56
+ 5. **Persist** — The patched code object is serialized to `.codesuture_store/` with JSON metadata. On subsequent runs, persisted patches load before the first function call.
57
+
58
+ ## Supported guard types
59
+
60
+ | Guard type | Triggers on | Example |
61
+ |---|---|---|
62
+ | `null_guard` | `AttributeError` on `None` | `user.profile.bio` when `profile is None` |
63
+ | `index_guard` | `IndexError` (list out of range) | `items[10]` when `len(items) == 2` |
64
+ | `key_guard` | `KeyError` | `cfg["timeout"]` when key missing |
65
+ | `type_coercion_guard` | `TypeError` (conversion failure) | `int("not_a_number")` |
66
+ | `subscript_guard` | `TypeError` subscripting `None` | `data["key"]` when `data is None` |
67
+ | `chain_subscript_guard` | Nested subscript on `None` | `data["user"]["name"]` |
68
+ | `division_guard` | `ZeroDivisionError` | `x / count` when `count == 0` |
69
+ | `str_coerce_guard` | `TypeError` (str + non-str) | `"age: " + 25` |
70
+ | `file_guard` | `FileNotFoundError` | `open(path)` when file missing |
71
+ | `callable_guard` | `TypeError` calling `None` | `func()` when `func is None` |
72
+
73
+ ## CLI reference
74
+
75
+ | Command | Flags | What it does |
76
+ |---|---|---|
77
+ | `codesuture run <script>` | | Run script with live patching enabled |
78
+ | `codesuture run <script>` | `--verbose` | Show patch diffs and instruction deltas |
79
+ | `codesuture run <script>` | `--shadow` | Warn if patched functions return sentinel values |
80
+ | `codesuture run <script>` | `--dry-run` | Show what would be patched without applying |
81
+ | `codesuture run <script>` | `--ttl DAYS` | Set patch expiry (default: 7 days) |
82
+ | `codesuture run <script>` | `--retries N` | Max re-execution attempts (default: 3) |
83
+ | `codesuture audit` | | Show all active patches in a formatted table |
84
+ | `codesuture rollback <name>` | | Remove persisted patch for one function |
85
+ | `codesuture rollback` | `--all` | Remove ALL patches + fingerprint registry |
86
+ | `codesuture rollback` | `--dry-run` | List what would be removed |
87
+
88
+ ## Dark upgrades
89
+
90
+ - **D1 — Semantic diff safety gate**: Rejects patches that modify too many instructions, preventing runaway bytecode corruption.
91
+ - **D2 — Caller-aware patch propagation**: Propagates patches to closures and bound methods via `gc.get_referrers`.
92
+ - **D3 — Shadow execution mode**: Monitors patched function return values and warns when sentinel defaults leak downstream.
93
+ - **D4 — Patch expiry TTL**: Warns when patches exceed their time-to-live, nudging developers to fix the root cause in source.
94
+ - **D5 — Bytecode fingerprint registry**: Caches crash patterns by bytecode hash for instant guard selection on repeated failures.
95
+ - **D6 — Audit command**: Displays all active patches in a formatted table with function name, guard type, age, and rollback hints.
96
+
97
+ ## Limitations
98
+
99
+ - **Python 3.11+ only** — CodeSuture relies on `PUSH_NULL`, `PRECALL`, and `POP_JUMP_FORWARD_IF_*` opcodes introduced in CPython 3.11.
100
+ - **Async not yet supported** — `async def` functions and coroutines are not patched.
101
+ - **Semantic bugs not patchable** — CodeSuture fixes structural crashes (null access, missing keys, type mismatches). It cannot fix logic errors where the code runs but produces wrong results.
102
+ - **Single-process scope** — Patches are applied per-process. Multi-process or distributed systems need separate CodeSuture instances.
103
+
104
+ ## License
105
+
106
+ MIT License. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,30 @@
1
+ codesuture/__init__.py,sha256=LBK46heutvn3KmsCrKIYu8RQikbfnjZaj2xFrXaeCzQ,22
2
+ codesuture/__main__.py,sha256=BbUA6Fvc9zApaO4IGe9F8meluGvNQNMOr_Cp_aq4y6k,75
3
+ codesuture/_eval_fix.py,sha256=b7Qnx3FySkEwTjRSJmyOxPfQd5ihe5hO4KqFUa7MiW0,121
4
+ codesuture/audit.py,sha256=tUitXsNc3LqYvcB4iDqL7IFeGUkyTrj3LZPxCW6uWdo,4376
5
+ codesuture/cli.py,sha256=vcvQi83K1GDHqrEnDLFcIGQZ8L6nWZWaEfPt_fEwF54,6068
6
+ codesuture/code_replacer.py,sha256=UddSbpQ-5UQIWxrES6G8WUXQkpQmECRVlvh5BXK5mZM,2797
7
+ codesuture/codesuture_fix.py,sha256=u9-dYj8h_HsNKggRn6r0AB8wu9mofdAx-pbNLCEhUG0,3154
8
+ codesuture/debuggee.py,sha256=gavL8h28_olmUvhJrBC8jRbxmcnNz2cu8SbA-Vxz7bo,232
9
+ codesuture/diff_guard.py,sha256=1CWw-dIXajU4riN8PWkq5g-NEfHx03W8QmAtuDqHJ4U,972
10
+ codesuture/explain.py,sha256=7hg-S6xhc5k55kEkAjGNOC_C3_Ik33IpIAj0n2sJTXY,5066
11
+ codesuture/fingerprint.py,sha256=65wUsBsJ6iPMekIVkjZVDLeFVWx9uFG9cA92Y3ufIrM,1823
12
+ codesuture/guard_synthesizer.py,sha256=xNhc9U56sv2IlbRPG48QfttVv6KC-yglVAorU-c-slQ,21295
13
+ codesuture/knowledge.py,sha256=brZ8KWBXUq67KJUj7XE5toTB640rRhtzm9MurzK5xDo,1067
14
+ codesuture/middleware.py,sha256=RNmHt2mayMqLzGa174IQHJpF5TV4tF7_7BHTwD74-so,2633
15
+ codesuture/pattern_matcher.py,sha256=B1CxqiP7jE-yfiRsP80SpR7ywlTbLimAbuctfad8lvY,22105
16
+ codesuture/persistence.py,sha256=mIJgIF1ozcI-R9-ChqDTU2UxxK61tTbNa4UOzSHQOJY,12303
17
+ codesuture/rewind.py,sha256=U5YoGYwR2GSLQNX7j_uq-BCORazC7_E55VFrxZM6McE,1024
18
+ codesuture/rollback.py,sha256=B2CSdZnzflxHprnl97KY-DVCoTU5xIR07_vmAtKW5mE,2658
19
+ codesuture/sandbox.py,sha256=xuXcljY-dvRrhTPrK066B1w3ZkIt0Or-1nWWkK1Rk9M,3885
20
+ codesuture/shadow.py,sha256=hdNj5AoINEfbvhwjaA-cp-LW2WscKvqHKOK4P915qaE,637
21
+ codesuture/tracer.py,sha256=AfhamJV3F8PxGWWE87_eAOeWcNbOpCq3yXWP-BuG5b0,18923
22
+ codesuture/watcher.py,sha256=UStuV2jeNW4THXPbbxEzeW-RvI2W0GPn5OHKFcl6E-c,2293
23
+ codesuture/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ codesuture/plugins/autonomous.py,sha256=i9RFUYgYwP_oeXpvJXzxRVAjICkQEpN138vvcnBNUEo,2034
25
+ codesuture-0.5.0.dist-info/licenses/LICENSE,sha256=vIM1B2ElIJEtcTeUSgi1zL_ACNMZTHa73OfFGcy_6A0,2248
26
+ codesuture-0.5.0.dist-info/METADATA,sha256=KzErH1nZmwxmFzoZnQHcw7kVnL7FbcSpe1dqRX4HWyM,5967
27
+ codesuture-0.5.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
28
+ codesuture-0.5.0.dist-info/entry_points.txt,sha256=0qxyKDStiJLxP_0RmZhmxHoH-8mISXvulCP2cSyMbY4,51
29
+ codesuture-0.5.0.dist-info/top_level.txt,sha256=Hd9qfnlIMfoRq-xtdVRBBigFvJBX3Z9TY1QdD609uGw,11
30
+ codesuture-0.5.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ codesuture = codesuture.cli:main