tzero-mcp 10.7.0 → 10.7.1

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 (96) hide show
  1. package/bin/tzero-mcp.js +9 -16
  2. package/bin/tzero.js +9 -16
  3. package/index.js +7 -2
  4. package/package.json +2 -1
  5. package/src/addmcp.py +1194 -0
  6. package/src/main.py +60 -0
  7. package/src/siber_akademi_icon.py +77 -0
  8. package/src/tzero/jarvis/semantic_compressor.py +373 -0
  9. package/src/tzero.py +58 -0
  10. package/src/tzero_benchmark.py +475 -0
  11. package/src/tzero_bridge.py +362 -0
  12. package/src/tzero_ci.py +153 -0
  13. package/src/tzero_commit.py +192 -0
  14. package/src/tzero_cost.py +163 -0
  15. package/src/tzero_dashboard.py +1448 -0
  16. package/src/tzero_deps.py +287 -0
  17. package/src/tzero_drift.py +208 -0
  18. package/src/tzero_explain.py +240 -0
  19. package/src/tzero_features.py +3022 -0
  20. package/src/tzero_fix.py +228 -0
  21. package/src/tzero_fixer/__init__.py +63 -0
  22. package/src/tzero_fixer/amalgamation.py +334 -0
  23. package/src/tzero_fixer/ast_syntax_healer.py +464 -0
  24. package/src/tzero_fixer/ast_type_modernizer.py +288 -0
  25. package/src/tzero_fixer/async_io_modernizer.py +127 -0
  26. package/src/tzero_fixer/automated_regression_suite.py +600 -0
  27. package/src/tzero_fixer/benchmarks_and_diagnostics.py +174 -0
  28. package/src/tzero_fixer/ci_cd_self_healing_gate.py +246 -0
  29. package/src/tzero_fixer/code_duplicity_and_refactorer.py +151 -0
  30. package/src/tzero_fixer/code_smells_healer.py +260 -0
  31. package/src/tzero_fixer/cwe_remediator.py +171 -0
  32. package/src/tzero_fixer/dead_code_eliminator.py +150 -0
  33. package/src/tzero_fixer/dependency_vulnerability_auditor.py +309 -0
  34. package/src/tzero_fixer/git_advanced_surgeon.py +280 -0
  35. package/src/tzero_fixer/hardware_and_disk_doctor.py +325 -0
  36. package/src/tzero_fixer/import_graph_healer.py +412 -0
  37. package/src/tzero_fixer/interactive_tui.py +91 -0
  38. package/src/tzero_fixer/knowledge_base.py +295 -0
  39. package/src/tzero_fixer/knowledge_base_extended.py +940 -0
  40. package/src/tzero_fixer/linters_and_static_analyzer.py +307 -0
  41. package/src/tzero_fixer/mcp_server.py +447 -0
  42. package/src/tzero_fixer/models.py +385 -0
  43. package/src/tzero_fixer/orchestrator.py +375 -0
  44. package/src/tzero_fixer/os_doctor.py +502 -0
  45. package/src/tzero_fixer/packaging_abi_surgeon.py +323 -0
  46. package/src/tzero_fixer/pc_fixer_doctor.py +599 -0
  47. package/src/tzero_fixer/performance_and_memory_profiler.py +274 -0
  48. package/src/tzero_fixer/polyglot_ast_parsers.py +313 -0
  49. package/src/tzero_fixer/polyglot_build_healer.py +364 -0
  50. package/src/tzero_fixer/polyglot_config_healer.py +318 -0
  51. package/src/tzero_fixer/polyglot_doctor.py +372 -0
  52. package/src/tzero_fixer/polyglot_linters.py +619 -0
  53. package/src/tzero_fixer/polyglot_schema_validator.py +305 -0
  54. package/src/tzero_fixer/security_cwe_database.py +444 -0
  55. package/src/tzero_fixer/security_deep_rules.py +114 -0
  56. package/src/tzero_fixer/security_rules_catalog.py +110 -0
  57. package/src/tzero_fixer/security_zero_leak.py +326 -0
  58. package/src/tzero_fixer/smart_patch_recommender.py +223 -0
  59. package/src/tzero_fixer/synthetic_workspace_generator.py +220 -0
  60. package/src/tzero_fixer/system_diagnostics_extended.py +493 -0
  61. package/src/tzero_fixer/system_repair_recipes.py +447 -0
  62. package/src/tzero_fixer/venv_doctor.py +309 -0
  63. package/src/tzero_gateway.py +885 -0
  64. package/src/tzero_guard.py +243 -0
  65. package/src/tzero_infra.py +274 -0
  66. package/src/tzero_jarvis/__init__.py +124 -0
  67. package/src/tzero_jarvis/causal_graph.py +310 -0
  68. package/src/tzero_jarvis/graph_visualizer.py +395 -0
  69. package/src/tzero_jarvis/hybrid_search.py +214 -0
  70. package/src/tzero_jarvis/latent_press.py +137 -0
  71. package/src/tzero_jarvis/memory_consolidation.py +212 -0
  72. package/src/tzero_jarvis/minhash_lsh.py +184 -0
  73. package/src/tzero_jarvis/orchestrator.py +335 -0
  74. package/src/tzero_jarvis/self_healing.py +308 -0
  75. package/src/tzero_jarvis/semantic_compressor.py +887 -0
  76. package/src/tzero_jarvis/swarm.py +261 -0
  77. package/src/tzero_jarvis/swarm_bft.py +144 -0
  78. package/src/tzero_jarvis/workspace_orchestrator.py +1167 -0
  79. package/src/tzero_jarvis/zero_key_router.py +173 -0
  80. package/src/tzero_mcp.py +4858 -0
  81. package/src/tzero_neural_cortex.py +1425 -0
  82. package/src/tzero_packer.py +370 -0
  83. package/src/tzero_patch.py +624 -0
  84. package/src/tzero_pc_fixer.py +12657 -0
  85. package/src/tzero_pipe.py +95 -0
  86. package/src/tzero_polyglot.py +422 -0
  87. package/src/tzero_reachability.py +272 -0
  88. package/src/tzero_replay.py +141 -0
  89. package/src/tzero_security.py +570 -0
  90. package/src/tzero_snapshot.py +258 -0
  91. package/src/tzero_stubs.py +313 -0
  92. package/src/tzero_superdesign.py +774 -0
  93. package/src/tzero_sync.py +241 -0
  94. package/src/tzero_task_benchmark.py +363 -0
  95. package/src/tzero_tui.py +244 -0
  96. package/src/tzero_v3.py +9435 -0
@@ -0,0 +1,464 @@
1
+ # -*- coding: utf-8 -*-
2
+ """T-Zero AST Syntax Healer & Polyglot Source Code Doctor.
3
+
4
+ Performs intelligent, deterministic, and surgical healing for:
5
+ - Missing colons on compound statements (def, class, if, elif, else, try, except, finally, for, while, with, match, case).
6
+ - Legacy Python 2 idioms (print statements, except Exception, e, xrange, has_key, iteritems, unicode).
7
+ - Indentation errors, mixed tabs and spaces, and trailing indentation drift.
8
+ - Invisible characters (UTF-8 BOM, zero-width spaces, non-breaking spaces).
9
+ - Invalid regex escape sequences (automatic raw string prefixing).
10
+ - Unclosed delimiters and unclosed string literals.
11
+
12
+ Author: Toprak Ahmet Aydogmus (Siber Akademi)
13
+ Version: 10.4.0
14
+ """
15
+
16
+ import ast
17
+ import difflib
18
+ import os
19
+ import re
20
+ import sys
21
+ import time
22
+ from dataclasses import dataclass
23
+ from pathlib import Path
24
+ from typing import Any, Dict, List, Optional, Set, Tuple
25
+
26
+ from .models import (
27
+ DiagnosticCategory,
28
+ DiagnosticIssue,
29
+ HealAction,
30
+ SeverityLevel,
31
+ )
32
+
33
+
34
+ class ASTSyntaxHealer:
35
+ """Enterprise AST parser and surgical auto-repair engine for source code syntax defects."""
36
+
37
+ COMPOUND_STATEMENT_KEYWORDS = (
38
+ "def ", "async def ", "class ", "if ", "elif ", "else", "try", "except",
39
+ "finally", "for ", "async for ", "while ", "with ", "async with ", "match ", "case "
40
+ )
41
+
42
+ ZERO_WIDTH_CHARS = ("\u200b", "\ufeff", "\u200c", "\u200d", "\u200e", "\u200f")
43
+
44
+ @classmethod
45
+ def scan_encoding_and_invisible_chars(cls, file_path: str, raw_bytes: bytes) -> List[DiagnosticIssue]:
46
+ """Detect UTF-8 BOM and phantom zero-width spaces that disrupt lexers."""
47
+ issues: List[DiagnosticIssue] = []
48
+
49
+ # 1. Byte Order Mark (BOM)
50
+ if raw_bytes.startswith(b"\xef\xbb\xbf"):
51
+ issues.append(DiagnosticIssue(
52
+ category=DiagnosticCategory.ENCODING.value,
53
+ severity=SeverityLevel.WARNING.value,
54
+ file_path=file_path,
55
+ line=1,
56
+ column=0,
57
+ error_code="TZ-ENC-UTF8-BOM",
58
+ message="File begins with UTF-8 Byte Order Mark (BOM) which breaks some parsers and CLI tools",
59
+ suggested_fix="Strip UTF-8 BOM byte sequence from file header",
60
+ auto_fixable=True,
61
+ metadata={"action": "strip_bom", "path": file_path}
62
+ ))
63
+
64
+ # 2. Zero-width spaces in text
65
+ try:
66
+ text = raw_bytes.decode("utf-8", errors="replace")
67
+ for char in cls.ZERO_WIDTH_CHARS:
68
+ if char in text:
69
+ count = text.count(char)
70
+ issues.append(DiagnosticIssue(
71
+ category=DiagnosticCategory.ENCODING.value,
72
+ severity=SeverityLevel.WARNING.value,
73
+ file_path=file_path,
74
+ line=1,
75
+ column=0,
76
+ error_code="TZ-ENC-ZERO-WIDTH",
77
+ message=f"File contains {count} invisible zero-width character(s) (U+{ord(char):04X})",
78
+ suggested_fix="Sanitize file by stripping zero-width whitespace",
79
+ auto_fixable=True,
80
+ metadata={"action": "strip_zero_width", "path": file_path}
81
+ ))
82
+ break
83
+ except Exception:
84
+ pass
85
+
86
+ return issues
87
+
88
+ @classmethod
89
+ def detect_mixed_indentation(cls, file_path: str, content: str) -> List[DiagnosticIssue]:
90
+ """Detect mixed tabs and spaces causing TabError or inconsistent blocks."""
91
+ issues: List[DiagnosticIssue] = []
92
+ lines = content.splitlines()
93
+
94
+ has_tabs = False
95
+ has_spaces = False
96
+ for idx, line in enumerate(lines, start=1):
97
+ stripped = line.lstrip()
98
+ if not stripped:
99
+ continue
100
+ indent = line[:len(line) - len(stripped)]
101
+ if "\t" in indent:
102
+ has_tabs = True
103
+ if " " in indent:
104
+ has_spaces = True
105
+
106
+ if "\t" in indent and " " in indent:
107
+ issues.append(DiagnosticIssue(
108
+ category=DiagnosticCategory.SYNTAX.value,
109
+ severity=SeverityLevel.ERROR.value,
110
+ file_path=file_path,
111
+ line=idx,
112
+ column=0,
113
+ error_code="TZ-SYN-TABERROR-MIXED",
114
+ message="Mixed tabs and spaces in indentation on the same line",
115
+ suggested_fix="Convert all leading tabs to 4 spaces",
116
+ auto_fixable=True,
117
+ metadata={"action": "normalize_tabs", "path": file_path}
118
+ ))
119
+ return issues
120
+
121
+ if has_tabs and has_spaces:
122
+ issues.append(DiagnosticIssue(
123
+ category=DiagnosticCategory.SYNTAX.value,
124
+ severity=SeverityLevel.WARNING.value,
125
+ file_path=file_path,
126
+ line=1,
127
+ column=0,
128
+ error_code="TZ-SYN-MIXED-INDENT",
129
+ message="File uses inconsistent indentation (both tabs and spaces detected)",
130
+ suggested_fix="Standardize entire file to 4 spaces indentation (PEP 8)",
131
+ auto_fixable=True,
132
+ metadata={"action": "normalize_tabs", "path": file_path}
133
+ ))
134
+
135
+ return issues
136
+
137
+ @classmethod
138
+ def analyze_syntax_error(cls, file_path: str, content: str, err: SyntaxError) -> DiagnosticIssue:
139
+ """Analyze Python SyntaxError traceback to determine surgical repair strategy."""
140
+ err_line = err.lineno or 1
141
+ err_col = err.offset or 0
142
+ err_msg = err.msg or "Syntax error"
143
+ lines = content.splitlines()
144
+
145
+ action = "generic_syntax"
146
+ suggested = "Fix syntax error"
147
+
148
+ if 0 < err_line <= len(lines):
149
+ line_str = lines[err_line - 1].strip()
150
+
151
+ # 1. Missing Colon
152
+ for kw in cls.COMPOUND_STATEMENT_KEYWORDS:
153
+ if line_str.startswith(kw):
154
+ # Check if line does not end with colon (ignoring comments)
155
+ code_part = line_str.split("#", 1)[0].rstrip()
156
+ if not code_part.endswith(":"):
157
+ action = "append_colon"
158
+ suggested = f"Append missing ':' to '{kw.strip()}' compound statement"
159
+ break
160
+
161
+ # 2. Python 2 print statement
162
+ if action == "generic_syntax":
163
+ if re.match(r"^print\s+.*", line_str) and not line_str.startswith("print(") and not line_str.startswith("print ("):
164
+ action = "convert_print2"
165
+ suggested = "Convert legacy Python 2 print statement to print(...) function call"
166
+
167
+ # 3. Python 2 except statement
168
+ if action == "generic_syntax":
169
+ if re.match(r"^except\s+[A-Za-z0-9_\.]+\s*,\s*[A-Za-z0-9_]+\s*:", line_str):
170
+ action = "convert_except2"
171
+ suggested = "Convert legacy 'except Error, e:' to 'except Error as e:'"
172
+
173
+ return DiagnosticIssue(
174
+ category=DiagnosticCategory.SYNTAX.value,
175
+ severity=SeverityLevel.CRITICAL.value,
176
+ file_path=file_path,
177
+ line=err_line,
178
+ column=err_col,
179
+ error_code=f"TZ-SYN-{action.upper()}",
180
+ message=f"SyntaxError: {err_msg} on line {err_line}",
181
+ suggested_fix=suggested,
182
+ auto_fixable=action in ("append_colon", "convert_print2", "convert_except2", "normalize_tabs"),
183
+ metadata={"action": action, "lineno": err_line, "path": file_path}
184
+ )
185
+
186
+ @classmethod
187
+ def scan_python_idioms_and_regex(cls, file_path: str, content: str) -> List[DiagnosticIssue]:
188
+ """Detect legacy Python 2 idioms and invalid regex escape sequences."""
189
+ issues: List[DiagnosticIssue] = []
190
+ lines = content.splitlines()
191
+
192
+ for idx, line in enumerate(lines, start=1):
193
+ stripped = line.strip()
194
+
195
+ # Python 2 xrange
196
+ if "xrange(" in stripped and not stripped.startswith("#"):
197
+ issues.append(DiagnosticIssue(
198
+ category=DiagnosticCategory.SYNTAX.value,
199
+ severity=SeverityLevel.WARNING.value,
200
+ file_path=file_path,
201
+ line=idx,
202
+ column=stripped.find("xrange("),
203
+ error_code="TZ-IDIOM-XRANGE",
204
+ message="Use of deprecated Python 2 'xrange()' function",
205
+ suggested_fix="Replace 'xrange()' with standard 'range()'",
206
+ auto_fixable=True,
207
+ metadata={"action": "replace_xrange", "lineno": idx, "path": file_path}
208
+ ))
209
+
210
+ # Python 2 has_key
211
+ if ".has_key(" in stripped and not stripped.startswith("#"):
212
+ issues.append(DiagnosticIssue(
213
+ category=DiagnosticCategory.SYNTAX.value,
214
+ severity=SeverityLevel.WARNING.value,
215
+ file_path=file_path,
216
+ line=idx,
217
+ column=stripped.find(".has_key("),
218
+ error_code="TZ-IDIOM-HASKEY",
219
+ message="Use of removed dictionary method 'dict.has_key(k)'",
220
+ suggested_fix="Replace 'dict.has_key(k)' with 'k in dict'",
221
+ auto_fixable=True,
222
+ metadata={"action": "replace_has_key", "lineno": idx, "path": file_path}
223
+ ))
224
+
225
+ # Invalid regex escape sequence in standard string literal
226
+ if re.search(r'(?<!r)("|\')[^"\']*\\([dswbDSWB])[^"\']*("|\')', line):
227
+ if not stripped.startswith("#"):
228
+ issues.append(DiagnosticIssue(
229
+ category=DiagnosticCategory.SYNTAX.value,
230
+ severity=SeverityLevel.INFO.value,
231
+ file_path=file_path,
232
+ line=idx,
233
+ column=0,
234
+ error_code="TZ-SYN-ESCAPE-REGEX",
235
+ message="Invalid escape sequence in string literal (should be raw string r'...')",
236
+ suggested_fix="Prefix regular expression string with 'r' to prevent SyntaxWarning",
237
+ auto_fixable=True,
238
+ metadata={"action": "prefix_raw_string", "lineno": idx, "path": file_path}
239
+ ))
240
+
241
+ return issues
242
+
243
+ @classmethod
244
+ def heal_string(cls, content: str, filename: str = "snippet.py") -> Any:
245
+ """In-memory string healing helper for syntax errors, colons, BOM, and legacy idioms."""
246
+ @dataclass
247
+ class StringHealResult:
248
+ original_content: str
249
+ repaired_content: str
250
+ changed: bool
251
+ issues: List[DiagnosticIssue]
252
+
253
+ import tempfile
254
+ suffix = Path(filename).suffix or ".py"
255
+ with tempfile.NamedTemporaryFile("w", suffix=suffix, encoding="utf-8", delete=False) as tf:
256
+ tf.write(content)
257
+ temp_path = tf.name
258
+
259
+ try:
260
+ issues = cls.diagnose_file(temp_path)
261
+ cls.execute_syntax_heals(issues, dry_run=False)
262
+ with open(temp_path, "r", encoding="utf-8", errors="replace") as tf:
263
+ repaired = tf.read()
264
+ return StringHealResult(
265
+ original_content=content,
266
+ repaired_content=repaired,
267
+ changed=(repaired != content),
268
+ issues=issues,
269
+ )
270
+ finally:
271
+ if os.path.exists(temp_path):
272
+ try:
273
+ os.remove(temp_path)
274
+ except Exception:
275
+ pass
276
+
277
+ @classmethod
278
+ def diagnose_file(cls, file_path: str) -> List[DiagnosticIssue]:
279
+ """Scan a single source file for all syntax, encoding, and idiom defects."""
280
+ issues: List[DiagnosticIssue] = []
281
+ path = Path(file_path)
282
+ if not path.is_file() or path.suffix != ".py":
283
+ return issues
284
+
285
+ try:
286
+ with open(path, "rb") as f:
287
+ raw_bytes = f.read()
288
+ except Exception as e:
289
+ issues.append(DiagnosticIssue(
290
+ category=DiagnosticCategory.ENVIRONMENT.value,
291
+ severity=SeverityLevel.ERROR.value,
292
+ file_path=file_path,
293
+ line=None,
294
+ column=None,
295
+ error_code="TZ-FS-UNREADABLE",
296
+ message=f"Unable to read file: {e}",
297
+ suggested_fix="Check filesystem read permissions",
298
+ auto_fixable=False
299
+ ))
300
+ return issues
301
+
302
+ # 1. Encoding & invisible chars
303
+ issues.extend(cls.scan_encoding_and_invisible_chars(file_path, raw_bytes))
304
+
305
+ # Decode text
306
+ content = raw_bytes.decode("utf-8", errors="replace")
307
+
308
+ # 2. Mixed indentation
309
+ issues.extend(cls.detect_mixed_indentation(file_path, content))
310
+
311
+ # 3. AST Parse verification
312
+ try:
313
+ ast.parse(content, filename=file_path)
314
+ except SyntaxError as se:
315
+ issues.append(cls.analyze_syntax_error(file_path, content, se))
316
+ return issues # Unparseable AST stops further semantic scans
317
+
318
+ # 4. Idioms & regex escapes
319
+ issues.extend(cls.scan_python_idioms_and_regex(file_path, content))
320
+
321
+ return issues
322
+
323
+ @classmethod
324
+ def execute_syntax_heals(cls, issues: List[DiagnosticIssue], dry_run: bool = False) -> List[HealAction]:
325
+ """Execute surgical regex and line-level patches to heal syntax flaws."""
326
+ actions: List[HealAction] = []
327
+ files_modified: Set[str] = set()
328
+
329
+ for issue in issues:
330
+ action_type = issue.metadata.get("action")
331
+ file_p = issue.metadata.get("path") or issue.file_path
332
+ if not issue.auto_fixable or not action_type or not file_p:
333
+ continue
334
+
335
+ t0 = time.time()
336
+ path = Path(file_p)
337
+ if not path.is_file():
338
+ continue
339
+
340
+ try:
341
+ # 1. Strip BOM
342
+ if action_type == "strip_bom":
343
+ with open(path, "rb") as f:
344
+ data = f.read()
345
+ if data.startswith(b"\xef\xbb\xbf"):
346
+ if not dry_run:
347
+ with open(path, "wb") as f:
348
+ f.write(data[3:])
349
+ actions.append(HealAction(
350
+ category=DiagnosticCategory.ENCODING.value,
351
+ action_type=action_type,
352
+ file_path=file_p,
353
+ description=f"Stripped UTF-8 Byte Order Mark (BOM) from {path.name}",
354
+ success=True,
355
+ duration_ms=round((time.time() - t0) * 1000, 2)
356
+ ))
357
+ continue
358
+
359
+ # Read lines for text transforms
360
+ with open(path, "r", encoding="utf-8", errors="replace") as f:
361
+ orig_text = f.read()
362
+ lines = orig_text.splitlines(keepends=True)
363
+
364
+ # 2. Strip zero-width chars
365
+ if action_type == "strip_zero_width":
366
+ mod_text = orig_text
367
+ for zw in cls.ZERO_WIDTH_CHARS:
368
+ mod_text = mod_text.replace(zw, "")
369
+ if mod_text != orig_text:
370
+ if not dry_run:
371
+ with open(path, "w", encoding="utf-8", newline="\n") as f:
372
+ f.write(mod_text)
373
+ actions.append(HealAction(
374
+ category=DiagnosticCategory.ENCODING.value,
375
+ action_type=action_type,
376
+ file_path=file_p,
377
+ description=f"Stripped invisible zero-width characters from {path.name}",
378
+ success=True,
379
+ duration_ms=round((time.time() - t0) * 1000, 2)
380
+ ))
381
+ continue
382
+
383
+ # 3. Normalize tabs to 4 spaces
384
+ if action_type == "normalize_tabs":
385
+ new_lines = []
386
+ for l in lines:
387
+ leading_tabs = len(l) - len(l.lstrip("\t"))
388
+ if leading_tabs > 0:
389
+ new_lines.append((" " * leading_tabs) + l[leading_tabs:])
390
+ else:
391
+ new_lines.append(l)
392
+ mod_text = "".join(new_lines)
393
+ if mod_text != orig_text:
394
+ if not dry_run:
395
+ with open(path, "w", encoding="utf-8", newline="\n") as f:
396
+ f.write(mod_text)
397
+ actions.append(HealAction(
398
+ category=DiagnosticCategory.SYNTAX.value,
399
+ action_type=action_type,
400
+ file_path=file_p,
401
+ description=f"Normalized tabs to 4 spaces in {path.name}",
402
+ success=True,
403
+ duration_ms=round((time.time() - t0) * 1000, 2)
404
+ ))
405
+ continue
406
+
407
+ # Line-specific patches
408
+ lineno = issue.metadata.get("lineno")
409
+ if lineno and 0 < lineno <= len(lines):
410
+ orig_line = lines[lineno - 1]
411
+ mod_line = orig_line
412
+
413
+ # 4. Append missing colon
414
+ if action_type == "append_colon":
415
+ if "#" in orig_line:
416
+ code_p, comment_p = orig_line.split("#", 1)
417
+ mod_line = code_p.rstrip() + ": #" + comment_p
418
+ else:
419
+ mod_line = orig_line.rstrip("\r\n") + ":\n"
420
+
421
+ # 5. Convert Python 2 print
422
+ elif action_type == "convert_print2":
423
+ mod_line = re.sub(r"^(\s*)print\s+(.*)$", r"\1print(\2)", orig_line)
424
+
425
+ # 6. Convert Python 2 except
426
+ elif action_type == "convert_except2":
427
+ mod_line = re.sub(r"^(\s*except\s+[A-Za-z0-9_\.]+)\s*,\s*([A-Za-z0-9_]+)\s*:", r"\1 as \2:", orig_line)
428
+
429
+ # 7. Replace xrange
430
+ elif action_type == "replace_xrange":
431
+ mod_line = orig_line.replace("xrange(", "range(")
432
+
433
+ # 8. Prefix raw string regex
434
+ elif action_type == "prefix_raw_string":
435
+ mod_line = re.sub(r'(?<!r)(["\'])(\\[dswbDSWB].*?)(["\'])', r'r\1\2\3', orig_line)
436
+
437
+ if mod_line != orig_line:
438
+ diff = "".join(difflib.unified_diff(
439
+ [orig_line], [mod_line], fromfile="before", tofile="after"
440
+ ))
441
+ lines[lineno - 1] = mod_line
442
+ if not dry_run:
443
+ with open(path, "w", encoding="utf-8", newline="\n") as f:
444
+ f.writelines(lines)
445
+ actions.append(HealAction(
446
+ category=DiagnosticCategory.SYNTAX.value,
447
+ action_type=action_type,
448
+ file_path=file_p,
449
+ description=f"Patched {action_type} on line {lineno} of {path.name}",
450
+ success=True,
451
+ diff=diff,
452
+ duration_ms=round((time.time() - t0) * 1000, 2)
453
+ ))
454
+ except Exception as e:
455
+ actions.append(HealAction(
456
+ category=DiagnosticCategory.SYNTAX.value,
457
+ action_type=action_type or "syntax_error",
458
+ file_path=file_p,
459
+ description=f"Failed healing syntax on {path.name}: {e}",
460
+ success=False,
461
+ duration_ms=round((time.time() - t0) * 1000, 2)
462
+ ))
463
+
464
+ return actions