codesuture 0.5.0__tar.gz

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 (44) hide show
  1. codesuture-0.5.0/LICENSE +33 -0
  2. codesuture-0.5.0/PKG-INFO +106 -0
  3. codesuture-0.5.0/README.md +85 -0
  4. codesuture-0.5.0/codesuture/__init__.py +1 -0
  5. codesuture-0.5.0/codesuture/__main__.py +5 -0
  6. codesuture-0.5.0/codesuture/_eval_fix.py +5 -0
  7. codesuture-0.5.0/codesuture/audit.py +127 -0
  8. codesuture-0.5.0/codesuture/cli.py +127 -0
  9. codesuture-0.5.0/codesuture/code_replacer.py +82 -0
  10. codesuture-0.5.0/codesuture/codesuture_fix.py +85 -0
  11. codesuture-0.5.0/codesuture/debuggee.py +7 -0
  12. codesuture-0.5.0/codesuture/diff_guard.py +27 -0
  13. codesuture-0.5.0/codesuture/explain.py +147 -0
  14. codesuture-0.5.0/codesuture/fingerprint.py +52 -0
  15. codesuture-0.5.0/codesuture/guard_synthesizer.py +607 -0
  16. codesuture-0.5.0/codesuture/knowledge.py +35 -0
  17. codesuture-0.5.0/codesuture/middleware.py +86 -0
  18. codesuture-0.5.0/codesuture/pattern_matcher.py +555 -0
  19. codesuture-0.5.0/codesuture/persistence.py +330 -0
  20. codesuture-0.5.0/codesuture/plugins/__init__.py +0 -0
  21. codesuture-0.5.0/codesuture/plugins/autonomous.py +64 -0
  22. codesuture-0.5.0/codesuture/rewind.py +43 -0
  23. codesuture-0.5.0/codesuture/rollback.py +85 -0
  24. codesuture-0.5.0/codesuture/sandbox.py +105 -0
  25. codesuture-0.5.0/codesuture/shadow.py +20 -0
  26. codesuture-0.5.0/codesuture/tracer.py +447 -0
  27. codesuture-0.5.0/codesuture/watcher.py +78 -0
  28. codesuture-0.5.0/codesuture.egg-info/PKG-INFO +106 -0
  29. codesuture-0.5.0/codesuture.egg-info/SOURCES.txt +42 -0
  30. codesuture-0.5.0/codesuture.egg-info/dependency_links.txt +1 -0
  31. codesuture-0.5.0/codesuture.egg-info/entry_points.txt +2 -0
  32. codesuture-0.5.0/codesuture.egg-info/requires.txt +4 -0
  33. codesuture-0.5.0/codesuture.egg-info/top_level.txt +1 -0
  34. codesuture-0.5.0/pyproject.toml +37 -0
  35. codesuture-0.5.0/setup.cfg +4 -0
  36. codesuture-0.5.0/setup.py +3 -0
  37. codesuture-0.5.0/tests/test_codesuture_debuggee.py +17 -0
  38. codesuture-0.5.0/tests/test_e2e.py +51 -0
  39. codesuture-0.5.0/tests/test_guard_synthesizer.py +36 -0
  40. codesuture-0.5.0/tests/test_harness.py +20 -0
  41. codesuture-0.5.0/tests/test_harness2.py +16 -0
  42. codesuture-0.5.0/tests/test_new_guards.py +68 -0
  43. codesuture-0.5.0/tests/test_pattern_matcher.py +89 -0
  44. codesuture-0.5.0/tests/test_unknown_bug.py +9 -0
@@ -0,0 +1,33 @@
1
+ <<<<<<< HEAD
2
+
3
+
4
+ Copyright <2026> <MUHAMMAD ABUBAKAR>
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
+
10
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11
+ =======
12
+ MIT License
13
+
14
+ Copyright (c) 2026 MUHAMMAD ABUBAKAR
15
+
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ of this software and associated documentation files (the "Software"), to deal
18
+ in the Software without restriction, including without limitation the rights
19
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ copies of the Software, and to permit persons to whom the Software is
21
+ furnished to do so, subject to the following conditions:
22
+
23
+ The above copyright notice and this permission notice shall be included in all
24
+ copies or substantial portions of the Software.
25
+
26
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32
+ SOFTWARE.
33
+ >>>>>>> 354c8c39be3e4e36096d5b2725b1828a0d53729f
@@ -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,85 @@
1
+ # CodeSuture
2
+
3
+ > Runtime Python bytecode patcher. Catches crashes, synthesizes guards, rewrites functions in-memory, and persists fixes across runs.
4
+
5
+ ## What it does
6
+
7
+ 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.
8
+
9
+ ## Quick start
10
+
11
+ ```bash
12
+ pip install codesuture
13
+ ```
14
+
15
+ ```bash
16
+ codesuture run your_buggy_script.py
17
+ ```
18
+
19
+ ```
20
+ [CodeSuture] Caught AttributeError: 'NoneType' object has no attribute 'bio'
21
+ [CodeSuture] Applying null_guard on 'profile' ...
22
+ [CodeSuture] Patch applied to get_bio().
23
+ [CodeSuture] Re-executing after 1 patch(es)...
24
+
25
+ Session summary:
26
+ Patches applied: 1
27
+ ```
28
+
29
+ ## How it works
30
+
31
+ 1. **Catch** — A `sys.settrace` callback intercepts exceptions at the exact frame and instruction offset where they occur.
32
+ 2. **Analyze** — The pattern matcher disassembles the function's bytecode, identifies the failing variable/operation, and selects the appropriate guard type.
33
+ 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.
34
+ 4. **Rewind** — Execution restarts from the top of the patched function. The guard prevents the same crash from recurring.
35
+ 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.
36
+
37
+ ## Supported guard types
38
+
39
+ | Guard type | Triggers on | Example |
40
+ |---|---|---|
41
+ | `null_guard` | `AttributeError` on `None` | `user.profile.bio` when `profile is None` |
42
+ | `index_guard` | `IndexError` (list out of range) | `items[10]` when `len(items) == 2` |
43
+ | `key_guard` | `KeyError` | `cfg["timeout"]` when key missing |
44
+ | `type_coercion_guard` | `TypeError` (conversion failure) | `int("not_a_number")` |
45
+ | `subscript_guard` | `TypeError` subscripting `None` | `data["key"]` when `data is None` |
46
+ | `chain_subscript_guard` | Nested subscript on `None` | `data["user"]["name"]` |
47
+ | `division_guard` | `ZeroDivisionError` | `x / count` when `count == 0` |
48
+ | `str_coerce_guard` | `TypeError` (str + non-str) | `"age: " + 25` |
49
+ | `file_guard` | `FileNotFoundError` | `open(path)` when file missing |
50
+ | `callable_guard` | `TypeError` calling `None` | `func()` when `func is None` |
51
+
52
+ ## CLI reference
53
+
54
+ | Command | Flags | What it does |
55
+ |---|---|---|
56
+ | `codesuture run <script>` | | Run script with live patching enabled |
57
+ | `codesuture run <script>` | `--verbose` | Show patch diffs and instruction deltas |
58
+ | `codesuture run <script>` | `--shadow` | Warn if patched functions return sentinel values |
59
+ | `codesuture run <script>` | `--dry-run` | Show what would be patched without applying |
60
+ | `codesuture run <script>` | `--ttl DAYS` | Set patch expiry (default: 7 days) |
61
+ | `codesuture run <script>` | `--retries N` | Max re-execution attempts (default: 3) |
62
+ | `codesuture audit` | | Show all active patches in a formatted table |
63
+ | `codesuture rollback <name>` | | Remove persisted patch for one function |
64
+ | `codesuture rollback` | `--all` | Remove ALL patches + fingerprint registry |
65
+ | `codesuture rollback` | `--dry-run` | List what would be removed |
66
+
67
+ ## Dark upgrades
68
+
69
+ - **D1 — Semantic diff safety gate**: Rejects patches that modify too many instructions, preventing runaway bytecode corruption.
70
+ - **D2 — Caller-aware patch propagation**: Propagates patches to closures and bound methods via `gc.get_referrers`.
71
+ - **D3 — Shadow execution mode**: Monitors patched function return values and warns when sentinel defaults leak downstream.
72
+ - **D4 — Patch expiry TTL**: Warns when patches exceed their time-to-live, nudging developers to fix the root cause in source.
73
+ - **D5 — Bytecode fingerprint registry**: Caches crash patterns by bytecode hash for instant guard selection on repeated failures.
74
+ - **D6 — Audit command**: Displays all active patches in a formatted table with function name, guard type, age, and rollback hints.
75
+
76
+ ## Limitations
77
+
78
+ - **Python 3.11+ only** — CodeSuture relies on `PUSH_NULL`, `PRECALL`, and `POP_JUMP_FORWARD_IF_*` opcodes introduced in CPython 3.11.
79
+ - **Async not yet supported** — `async def` functions and coroutines are not patched.
80
+ - **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.
81
+ - **Single-process scope** — Patches are applied per-process. Multi-process or distributed systems need separate CodeSuture instances.
82
+
83
+ ## License
84
+
85
+ MIT License. See [LICENSE](LICENSE) for details.
@@ -0,0 +1 @@
1
+ __version__ = "0.5.0"
@@ -0,0 +1,5 @@
1
+
2
+ from codesuture.cli import main
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -0,0 +1,5 @@
1
+ import sys
2
+ from codesuture.codesuture_fix import apply_fix_with_info
3
+
4
+ result = apply_fix_with_info()
5
+ print(result)
@@ -0,0 +1,127 @@
1
+ import os
2
+ import json
3
+ import sys
4
+ from datetime import datetime
5
+
6
+ def run_audit(patch_store_path: str = None):
7
+
8
+ candidates = [
9
+ ".codesuture_cache", ".codesuture_store",
10
+ ".codesuture", "codesuture_patches"
11
+ ]
12
+ store = patch_store_path
13
+ if not store:
14
+ for c in candidates:
15
+ if os.path.exists(c):
16
+ store = c
17
+ break
18
+
19
+ if not store:
20
+ print("[CodeSuture] No patch store found. Nothing has been patched yet.")
21
+ return
22
+
23
+ patches = _load_all_patches(store)
24
+
25
+ if not patches:
26
+ print("[CodeSuture] Patch store exists but is empty.")
27
+ return
28
+
29
+ now = datetime.utcnow()
30
+
31
+ col_func = max(18, max(len(p.get("func_name","?")) for p in patches) + 2)
32
+ col_guard = 12
33
+ col_target = 10
34
+ col_default = 10
35
+ col_age = 7
36
+
37
+ try:
38
+ "|".encode(sys.stdout.encoding or 'ascii')
39
+ HAS_UNICODE = True
40
+ except Exception:
41
+ HAS_UNICODE = False
42
+
43
+ def row(f, g, t, d, a):
44
+ v = "|" if HAS_UNICODE else "|"
45
+ return (f"{v} {f:<{col_func}} {v} {g:<{col_guard}} {v} "
46
+ f"{t:<{col_target}} {v} {d:<{col_default}} {v} {a:<{col_age}} {v}")
47
+
48
+ if HAS_UNICODE:
49
+ sep = (f"+-{'-'*col_func}-+-{'-'*col_guard}-+-"
50
+ f"{'-'*col_target}-+-{'-'*col_default}-+-{'-'*col_age}-+")
51
+ top = (f"+-{'-'*col_func}-+-{'-'*col_guard}-+-"
52
+ f"{'-'*col_target}-+-{'-'*col_default}-+-{'-'*col_age}-+++")
53
+ bot = (f"+-{'-'*col_func}-+-{'-'*col_guard}-+-"
54
+ f"{'-'*col_target}-+-{'-'*col_default}-+-{'-'*col_age}-+")
55
+ else:
56
+ sep = (f"|-{'*'*col_func}-+-{'*'*col_guard}-+-"
57
+ f"{'*'*col_target}-+-{'*'*col_default}-+-{'*'*col_age}-|")
58
+ top = sep
59
+ bot = sep
60
+
61
+ print()
62
+ print(" CodeSuture Audit Report")
63
+ print()
64
+ print(top)
65
+ print(row("Function", "Guard", "Target", "Default", "Age"))
66
+ print(sep)
67
+
68
+ oldest_days = 0
69
+ expired = 0
70
+ for p in patches:
71
+ func = p.get("func_name", "?")
72
+ guard = p.get("guard_type", "?")
73
+ target = p.get("target", "?")
74
+ default = repr(p.get("default_value", "?"))[:col_default]
75
+ age_str = "?"
76
+ ttl_days = p.get("ttl_days", 7)
77
+ if "patched_at" in p:
78
+ try:
79
+ dt = datetime.fromisoformat(p["patched_at"])
80
+ days = (now - dt).days
81
+ oldest_days = max(oldest_days, days)
82
+ age_str = f"{days}d"
83
+ if days > ttl_days:
84
+ age_str += " [WARN]"
85
+ expired += 1
86
+ except Exception:
87
+ pass
88
+ print(row(func, guard, target, default, age_str))
89
+
90
+ print(bot)
91
+ print()
92
+ print(f" Total: {len(patches)} active patch(es). "
93
+ f"Oldest: {oldest_days}d. "
94
+ f"{'[WARN] ' + str(expired) + ' expired - run codesuture rollback to clear.' if expired else 'All within TTL.'}")
95
+ print()
96
+ print(" Run 'codesuture rollback <function_name>' to remove a patch.")
97
+ print(" Run 'codesuture rollback --all' to clear everything.")
98
+ print()
99
+
100
+ def _load_all_patches(store_path: str) -> list[dict]:
101
+
102
+ patches = []
103
+ if os.path.isdir(store_path):
104
+ for root, dirs, files in os.walk(store_path):
105
+ for fname in files:
106
+ fpath = os.path.join(root, fname)
107
+ if fname.endswith(".json") and os.path.isfile(fpath):
108
+ try:
109
+ with open(fpath, "r", encoding="utf-8") as f:
110
+ data = json.load(f)
111
+ if isinstance(data, list):
112
+ patches.extend(data)
113
+ elif isinstance(data, dict):
114
+ patches.append(data)
115
+ except Exception:
116
+ pass
117
+ elif os.path.isfile(store_path):
118
+ try:
119
+ with open(store_path, "r", encoding="utf-8") as f:
120
+ data = json.load(f)
121
+ if isinstance(data, list):
122
+ patches = data
123
+ elif isinstance(data, dict):
124
+ patches = list(data.values())
125
+ except Exception:
126
+ pass
127
+ return patches
@@ -0,0 +1,127 @@
1
+ import sys
2
+ import argparse
3
+ from codesuture.tracer import install, uninstall
4
+
5
+ def main():
6
+ parser = argparse.ArgumentParser(prog='codesuture',
7
+ description='Runtime Python bytecode patcher with self-healing re-execution')
8
+ parser.add_argument('--version', action='version', version='codesuture 0.5.0')
9
+ sub = parser.add_subparsers(dest='command', required=True)
10
+
11
+ run_parser = sub.add_parser('run', help='Run a script with live patching')
12
+ run_parser.add_argument('script', help='Target script to run')
13
+ run_parser.add_argument('--dry-run', action='store_true', help='Show what would be patched without applying')
14
+ run_parser.add_argument('--log', metavar='FILE', help='Append patch events (JSON lines) to FILE')
15
+ run_parser.add_argument('--retries', type=int, default=3, metavar='N', help='Max patching attempts (default: 3)')
16
+ run_parser.add_argument('--self-test', action='store_true', help='Corrupt the engine to test self-healing')
17
+ run_parser.add_argument('--autonomous', action='store_true', help='Enable autonomous LLM bug-fixing')
18
+ run_parser.add_argument('--verbose', action='store_true', help='Show detailed debug output')
19
+ run_parser.add_argument('--shadow', action='store_true', help='Warn if patched functions return sentinel values')
20
+ run_parser.add_argument('--ttl', type=int, default=7, metavar='DAYS', help='Patch TTL in days (default: 7)')
21
+
22
+ sub.add_parser('audit', help='Show all active patches')
23
+
24
+ rb_parser = sub.add_parser('rollback', help='Remove persisted patches')
25
+ rb_parser.add_argument('function_name', nargs='?', default=None, help='Function name to roll back')
26
+ rb_parser.add_argument('--all', action='store_true', dest='rollback_all', help='Remove ALL patches + fingerprints')
27
+ rb_parser.add_argument('--dry-run', action='store_true', dest='rollback_dry_run', help='List what would be removed')
28
+
29
+ watch_parser = sub.add_parser('watch', help='Watch and auto-restart a script with live patching')
30
+ watch_parser.add_argument('script', help='Target script to watch')
31
+ watch_parser.add_argument('--max-restarts', type=int, default=10, metavar='N',
32
+ help='Maximum number of restarts (default: 10)')
33
+ watch_parser.add_argument('--shadow', action='store_true', help='Enable shadow mode warnings')
34
+ watch_parser.add_argument('--verbose', action='store_true', help='Show detailed debug output')
35
+
36
+ explain_parser = sub.add_parser('explain', help='Show detailed explanation of active patches')
37
+ explain_parser.add_argument('func_name', nargs='?', default=None, help='Function name to explain')
38
+
39
+ args = parser.parse_args()
40
+
41
+ if args.command == 'audit':
42
+ from codesuture.audit import run_audit
43
+ run_audit()
44
+ return
45
+
46
+ if args.command == 'rollback':
47
+ from codesuture.rollback import rollback_function, rollback_all, rollback_dry_run
48
+ if args.rollback_dry_run:
49
+ rollback_dry_run()
50
+ elif args.rollback_all:
51
+ rollback_all()
52
+ elif args.function_name:
53
+ rollback_function(args.function_name)
54
+ else:
55
+ print("[CodeSuture] Usage: codesuture rollback <function_name> | --all | --dry-run")
56
+ return
57
+
58
+ if args.command == 'watch':
59
+ from codesuture.watcher import watch
60
+ exit_code = watch(
61
+ args.script,
62
+ max_restarts=args.max_restarts,
63
+ shadow=args.shadow,
64
+ verbose=args.verbose,
65
+ )
66
+ sys.exit(exit_code)
67
+
68
+ if args.command == 'explain':
69
+ from codesuture.explain import run_explain
70
+ run_explain(args.func_name)
71
+ return
72
+
73
+ if getattr(args, 'autonomous', False):
74
+ try:
75
+ import llama_cpp
76
+ except ImportError:
77
+ print("Autonomous mode requires llama-cpp-python. Install with: pip install codesuture[autonomous]")
78
+ sys.exit(1)
79
+
80
+ if args.command == 'run':
81
+ from codesuture.persistence import install_import_hook, make_persisted_patch_globals
82
+
83
+ install_import_hook()
84
+
85
+ if getattr(args, 'self_test', False):
86
+ import codesuture.pattern_matcher as pm
87
+ print("[CodeSuture] SELF-TEST: corrupting _infer_default -> None")
88
+ pm._infer_default = None
89
+ tracer = None
90
+ try:
91
+ with open(args.script, 'r', encoding='utf-8') as f:
92
+ source = f.read()
93
+ code = compile(source, args.script, 'exec')
94
+
95
+ tracer = install(dry_run=args.dry_run, log_file=args.log,
96
+ max_retries=args.retries,
97
+ autonomous=getattr(args, 'autonomous', False),
98
+ script_path=args.script, verbose=args.verbose,
99
+ shadow=args.shadow, ttl=args.ttl)
100
+ max_runs = args.retries + 1
101
+ for run in range(max_runs):
102
+ patched_before = tracer.stats['patched']
103
+ tracer._handled_exc_ids.clear()
104
+ try:
105
+ sys.settrace(tracer)
106
+ globs = make_persisted_patch_globals(
107
+ "__main__",
108
+ {'__name__': '__main__', '__file__': args.script},
109
+ )
110
+ exec(code, globs)
111
+ break
112
+ except Exception as e:
113
+ sys.settrace(None)
114
+ new_patches = tracer.stats['patched'] - patched_before
115
+ if new_patches > 0 and run < max_runs - 1:
116
+ print(f"[CodeSuture] Re-executing after {new_patches} patch(es)...")
117
+ continue
118
+ else:
119
+ print(f"[CodeSuture] Script exited with: {e}")
120
+ break
121
+ finally:
122
+ uninstall()
123
+ if tracer is not None:
124
+ tracer.report()
125
+
126
+ if __name__ == '__main__':
127
+ main()
@@ -0,0 +1,82 @@
1
+ import types
2
+ import inspect
3
+
4
+ def replace_function_code(func, new_code):
5
+
6
+ from codesuture.guard_synthesizer import propagate_patch
7
+ propagate_patch(func, new_code)
8
+
9
+ def get_function_from_frame(frame):
10
+ name = frame.f_code.co_name
11
+ code = frame.f_code
12
+
13
+ if name in frame.f_locals:
14
+ candidate = frame.f_locals[name]
15
+ if hasattr(candidate, '__code__') and candidate.__code__ is code:
16
+ return candidate
17
+
18
+ if name in frame.f_globals:
19
+ candidate = frame.f_globals[name]
20
+ if hasattr(candidate, '__code__') and candidate.__code__ is code:
21
+ return candidate
22
+
23
+ if name == '<module>':
24
+ return None
25
+
26
+ self_obj = frame.f_locals.get('self')
27
+ if self_obj is not None:
28
+ cls = type(self_obj)
29
+ method = getattr(cls, name, None)
30
+ if method is not None:
31
+ if isinstance(method, property) and method.fget is not None:
32
+ func = method.fget
33
+ else:
34
+ func = method
35
+ if hasattr(func, '__func__'):
36
+ func = func.__func__
37
+ if hasattr(func, '__code__') and func.__code__ is code:
38
+ return func
39
+
40
+ cls_obj = frame.f_locals.get('cls')
41
+ if cls_obj is not None and isinstance(cls_obj, type):
42
+ method = getattr(cls_obj, name, None)
43
+ if method is not None:
44
+ if isinstance(method, property) and method.fget is not None:
45
+ func = method.fget
46
+ else:
47
+ func = method
48
+ if hasattr(func, '__func__'):
49
+ func = func.__func__
50
+ if hasattr(func, '__code__') and func.__code__ is code:
51
+ return func
52
+
53
+ for val in frame.f_globals.values():
54
+ if isinstance(val, type):
55
+ method = getattr(val, name, None)
56
+ if method is not None:
57
+ if isinstance(method, property) and method.fget is not None:
58
+ func = method.fget
59
+ else:
60
+ func = method
61
+ if hasattr(func, '__func__'):
62
+ func = func.__func__
63
+ if hasattr(func, '__code__') and func.__code__ is code:
64
+ return func
65
+
66
+ for val in frame.f_globals.values():
67
+ if hasattr(val, '__wrapped__'):
68
+ inner = val.__wrapped__
69
+ if hasattr(inner, '__code__') and inner.__code__ is code:
70
+ return inner
71
+ if hasattr(val, '__code__') and val.__code__ is code:
72
+ return val
73
+
74
+ return None
75
+
76
+ def get_source_from_frame(frame):
77
+
78
+ func = get_function_from_frame(frame)
79
+ try:
80
+ return inspect.getsource(func)
81
+ except Exception as e:
82
+ return f"# Could not get source: {e}\n"
@@ -0,0 +1,85 @@
1
+ import sys
2
+ import inspect
3
+ import os
4
+ import types
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
+ def apply_fix(exc_type_name: str = None, exc_msg: str = None) -> str:
11
+
12
+ exc_info = sys.exc_info()
13
+ target_frame = None
14
+ tb = None
15
+
16
+ if exc_info[0] is not None:
17
+ if exc_type_name is None:
18
+ exc_type_name = exc_info[0].__name__
19
+ if exc_msg is None:
20
+ exc_msg = str(exc_info[1])
21
+
22
+ tb = exc_info[2]
23
+ curr_tb = tb
24
+ while curr_tb and curr_tb.tb_next:
25
+ curr_tb = curr_tb.tb_next
26
+ if curr_tb:
27
+ target_frame = curr_tb.tb_frame
28
+
29
+ if target_frame is None:
30
+ if exc_type_name is None or exc_msg is None:
31
+ return "ERROR: No active exception. You must provide exc_type_name and exc_msg if paused on an unhandled exception."
32
+
33
+ for fi in inspect.stack():
34
+ frame = fi.frame
35
+ filename = frame.f_code.co_filename
36
+ func_name = frame.f_code.co_name
37
+
38
+ if 'pydevd' in filename.lower() and func_name in ('evaluate_expression', 'new_func', '_run_with_unblock_threads', '_run_with_interrupt_thread'):
39
+ potential_frame = frame.f_locals.get('frame')
40
+ if isinstance(potential_frame, types.FrameType):
41
+ target_frame = potential_frame
42
+ break
43
+
44
+ if func_name in ('apply_fix', 'apply_fix_with_info', '<module>', 'Exec', 'exec'):
45
+ continue
46
+ if any(x in filename.lower() for x in ('pydevd', 'debugpy', 'threading', 'importlib')):
47
+ continue
48
+
49
+ basename = os.path.basename(filename)
50
+ internal_files = (
51
+ 'codesuture_fix.py', 'pattern_matcher.py', 'guard_synthesizer.py',
52
+ 'code_replacer.py', 'rewind.py', 'tracer.py', 'debuggee.py'
53
+ )
54
+ if basename in internal_files:
55
+ continue
56
+
57
+ target_frame = frame
58
+ break
59
+
60
+ if target_frame is None:
61
+ return "ERROR: No paused frame found"
62
+
63
+ class FakeExc:
64
+ def __init__(self, msg):
65
+ self._msg = msg
66
+ def __str__(self):
67
+ return self._msg
68
+
69
+ exc_type = FakeExc
70
+ exc_type.__name__ = exc_type_name
71
+ exc_value = FakeExc(exc_msg)
72
+
73
+ spec = analyze_exception(target_frame, exc_type, exc_value, tb)
74
+ if spec is None:
75
+ return f"ERROR: No deterministic patch for {exc_type_name}"
76
+
77
+ try:
78
+ func = get_function_from_frame(target_frame)
79
+ new_bc = synthesize_guarded_code(target_frame.f_code, spec)
80
+ new_code = new_bc.to_code()
81
+ replace_function_code(func, new_code)
82
+ rewind_frame_to_start(target_frame, target_frame.f_code)
83
+ return f"OK: patched {target_frame.f_code.co_name} ({spec.strategy} on {spec.var_name})"
84
+ except Exception as e:
85
+ return f"ERROR: {e}"
@@ -0,0 +1,7 @@
1
+ import debugpy
2
+
3
+ def enable(port=5678):
4
+ debugpy.listen(port)
5
+ print(f"[CodeSuture] Waiting for debugger on port {port}...")
6
+ debugpy.wait_for_client()
7
+ print("[CodeSuture] Debugger attached. Live patching active.")