aike-code-sentinel 0.1.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.
code_sentinel/fixer.py ADDED
@@ -0,0 +1,329 @@
1
+ """
2
+ 自动修复引擎 — Code Sentinel 修复模块
3
+ ======================================
4
+ 对 scanner.py 检测到的问题生成修复建议和 diff。
5
+
6
+ 集成 Genome Core 四层自审 (full_review) 对修复代码进行验证。
7
+ """
8
+
9
+ import os
10
+ import difflib
11
+ from dataclasses import dataclass, field
12
+ from pathlib import Path
13
+ from typing import List, Optional
14
+
15
+ # ── Genome Core 集成 ──
16
+ try:
17
+ from genome_core.security import full_review, check_dangerous
18
+ HAS_GENOME_CORE = True
19
+ except ImportError:
20
+ HAS_GENOME_CORE = False
21
+
22
+
23
+ @dataclass
24
+ class FixAction:
25
+ """单个修复操作"""
26
+ file: str
27
+ line: int
28
+ description: str
29
+ original_code: str
30
+ fixed_code: str
31
+ category: str # security / performance / smell / dependency
32
+ confidence: float = 0.0
33
+
34
+ def to_diff(self) -> str:
35
+ """生成统一 diff 格式"""
36
+ original_lines = self.original_code.split("\n")
37
+ fixed_lines = self.fixed_code.split("\n")
38
+ diff = difflib.unified_diff(
39
+ original_lines,
40
+ fixed_lines,
41
+ fromfile=f"a/{self.file}",
42
+ tofile=f"b/{self.file}",
43
+ lineterm="",
44
+ )
45
+ return "\n".join(diff)
46
+
47
+
48
+ @dataclass
49
+ class FixResult:
50
+ """修复结果"""
51
+ file: str
52
+ actions: List[FixAction]
53
+ genome_review_passed: bool = False
54
+ genome_review_details: dict = field(default_factory=dict)
55
+
56
+ def to_dict(self) -> dict:
57
+ return {
58
+ "file": self.file,
59
+ "actions": [
60
+ {
61
+ "file": a.file,
62
+ "line": a.line,
63
+ "description": a.description,
64
+ "category": a.category,
65
+ "confidence": a.confidence,
66
+ "diff": a.to_diff(),
67
+ }
68
+ for a in self.actions
69
+ ],
70
+ "genome_review_passed": self.genome_review_passed,
71
+ }
72
+
73
+
74
+ # ── 修复模式库 ──
75
+
76
+ FIX_TEMPLATES = {
77
+ "eval": {
78
+ "description": "将 eval() 替换为安全替代方案(ast.literal_eval 或 json.loads)",
79
+ "fix": "ast.literal_eval({arg})",
80
+ "import_add": "import ast",
81
+ },
82
+ "exec": {
83
+ "description": "避免使用 exec(),改用函数封装或字典映射",
84
+ "fix": "# TODO: 重构 exec() 调用 — 改用函数封装或字典映射\n# 原代码: {original}",
85
+ "import_add": None,
86
+ },
87
+ "os.system": {
88
+ "description": "将 os.system() 替换为 subprocess.run(..., check=True) 并过滤输入",
89
+ "fix": "subprocess.run({args!r}, shell=True, check=True)",
90
+ "import_add": "import subprocess",
91
+ },
92
+ "bare_except": {
93
+ "description": "为裸 except 指定具体的异常类型",
94
+ "fix": "except Exception as e:",
95
+ },
96
+ "file_read": {
97
+ "description": "对大文件使用分块读取模式",
98
+ "fix": "for chunk in iter(lambda: {fp}.read(8192), b''):\n process(chunk)",
99
+ },
100
+ }
101
+
102
+
103
+ class CodeFixer:
104
+ """Code Sentinel 修复引擎
105
+
106
+ 根据扫描结果自动生成修复建议和 diff,
107
+ 并通过 Genome Core 的四层自审对修复代码进行验证。
108
+ """
109
+
110
+ def __init__(self):
111
+ self._results: List[FixResult] = []
112
+
113
+ def generate_fixes(self, scan_results: dict) -> List[FixResult]:
114
+ """根据扫描结果生成所有修复"""
115
+ self._results.clear()
116
+
117
+ for category_key in ("security", "performance", "smell"):
118
+ for issue in scan_results.get(category_key, []):
119
+ fix = self._generate_single_fix(category_key, issue)
120
+ if fix:
121
+ self._results.append(fix)
122
+
123
+ # 依赖问题:生成 requirements.txt 修复
124
+ for dep_issue in scan_results.get("dependency", []):
125
+ fix = self._fix_dependency(dep_issue)
126
+ if fix:
127
+ self._results.append(fix)
128
+
129
+ return self._results
130
+
131
+ def _generate_single_fix(self, category: str, issue: dict) -> Optional[FixResult]:
132
+ """为单个问题生成修复"""
133
+ filepath = issue.get("file", "")
134
+ line = issue.get("line", 0)
135
+ code = issue.get("code", "")
136
+ description = issue.get("description", "")
137
+
138
+ # 安全检查修复
139
+ if category == "security":
140
+ fix_action = self._fix_security_issue(issue)
141
+ if fix_action:
142
+ # 通过 Genome Core 四层自审验证修复
143
+ review_passed = True
144
+ review_details = {}
145
+ if HAS_GENOME_CORE:
146
+ review_passed, review_details = full_review(fix_action.fixed_code)
147
+
148
+ return FixResult(
149
+ file=filepath,
150
+ actions=[fix_action],
151
+ genome_review_passed=review_passed,
152
+ genome_review_details=review_details,
153
+ )
154
+
155
+ # 代码异味修复
156
+ if category == "smell":
157
+ fix_action = self._fix_smell_issue(issue)
158
+ if fix_action:
159
+ return FixResult(file=filepath, actions=[fix_action])
160
+
161
+ # 性能问题修复
162
+ if category == "performance":
163
+ fix_action = self._fix_performance_issue(issue)
164
+ if fix_action:
165
+ return FixResult(file=filepath, actions=[fix_action])
166
+
167
+ return None
168
+
169
+ def _fix_security_issue(self, issue: dict) -> Optional[FixAction]:
170
+ """生成安全修复"""
171
+ code = issue.get("code", "")
172
+ filepath = issue.get("file", "")
173
+ line = issue.get("line", 0)
174
+
175
+ # 检测危险函数类型
176
+ for keyword, template in FIX_TEMPLATES.items():
177
+ if keyword in code:
178
+ if keyword in ("eval", "exec"):
179
+ if keyword == "eval":
180
+ # 提取 eval 的参数
181
+ arg_match = code[code.find("(") + 1 : code.rfind(")")].strip()
182
+ if arg_match:
183
+ fixed = f"ast.literal_eval({arg_match})"
184
+ return FixAction(
185
+ file=filepath,
186
+ line=line,
187
+ description=template["description"],
188
+ original_code=code,
189
+ fixed_code=fixed,
190
+ category="security",
191
+ confidence=0.85,
192
+ )
193
+ else:
194
+ # exec 无法直接替换,加注释提示
195
+ fixed = f"# FIXME: 避免使用 exec()\n# " + code
196
+ return FixAction(
197
+ file=filepath,
198
+ line=line,
199
+ description=template["description"],
200
+ original_code=code,
201
+ fixed_code=fixed,
202
+ category="security",
203
+ confidence=0.7,
204
+ )
205
+
206
+ if "os.system" in code:
207
+ import re
208
+ args_match = re.search(r"os\.system\((.+)\)", code)
209
+ if args_match:
210
+ args = args_match.group(1)
211
+ fixed = f"subprocess.run({args}, shell=True, check=True)"
212
+ return FixAction(
213
+ file=filepath,
214
+ line=line,
215
+ description=template["description"],
216
+ original_code=code,
217
+ fixed_code=fixed,
218
+ category="security",
219
+ confidence=0.8,
220
+ )
221
+
222
+ return None
223
+
224
+ def _fix_smell_issue(self, issue: dict) -> Optional[FixAction]:
225
+ """生成代码异味修复"""
226
+ description = issue.get("description", "")
227
+ filepath = issue.get("file", "")
228
+ line = issue.get("line", 0)
229
+ code = issue.get("code", "")
230
+
231
+ if "裸 except" in description:
232
+ # 修复裸 except
233
+ fixed = "except Exception as e:"
234
+ return FixAction(
235
+ file=filepath,
236
+ line=line,
237
+ description="为裸 except 指定具体的异常类型 Exception",
238
+ original_code=code,
239
+ fixed_code=fixed,
240
+ category="smell",
241
+ confidence=0.95,
242
+ )
243
+
244
+ if "参数过多" in description:
245
+ fixed = code + "\n # TODO: 考虑使用 *args, **kwargs 或数据类重构签名"
246
+ return FixAction(
247
+ file=filepath,
248
+ line=line,
249
+ description="参数过多,建议使用数据类或 *args/**kwargs",
250
+ original_code=code,
251
+ fixed_code=fixed,
252
+ category="smell",
253
+ confidence=0.6,
254
+ )
255
+
256
+ return None
257
+
258
+ def _fix_performance_issue(self, issue: dict) -> Optional[FixAction]:
259
+ """生成性能修复"""
260
+ description = issue.get("description", "")
261
+ filepath = issue.get("file", "")
262
+ line = issue.get("line", 0)
263
+ code = issue.get("code", "")
264
+
265
+ if "文件一次性读取" in code or "文件一次性读取" in description:
266
+ # 修复大文件读取
267
+ fixed = "# 使用分块读取替代:\nfor chunk in iter(lambda: f.read(8192), b''):\n process(chunk)"
268
+ return FixAction(
269
+ file=filepath,
270
+ line=line,
271
+ description="大文件应使用分块读取(chunked reading)",
272
+ original_code=code,
273
+ fixed_code=fixed,
274
+ category="performance",
275
+ confidence=0.8,
276
+ )
277
+
278
+ return None
279
+
280
+ def _fix_dependency(self, issue: dict) -> Optional[FixResult]:
281
+ """生成依赖修复"""
282
+ filepath = issue.get("file", "")
283
+ line = issue.get("line", 0)
284
+ package = issue.get("package", "")
285
+ version = issue.get("version", "")
286
+
287
+ if not package:
288
+ return None
289
+
290
+ # 生成修复后的版本行
291
+ safe_versions = {
292
+ "requests": ">=2.31.0",
293
+ "urllib3": ">=1.26.18",
294
+ "cryptography": ">=41.0.0",
295
+ "django": ">=4.2.7",
296
+ "flask": ">=2.3.3",
297
+ "pillow": ">=10.0.1",
298
+ "pyyaml": ">=6.0",
299
+ "jinja2": ">=3.1.3",
300
+ "werkzeug": ">=3.0.3",
301
+ "numpy": ">=1.26.0",
302
+ "protobuf": ">=3.18.3",
303
+ }
304
+
305
+ safe_ver = safe_versions.get(package.lower())
306
+ if not safe_ver:
307
+ return None
308
+
309
+ original_line = f"{package}{version}" if version else package
310
+ fixed_line = f"{package}{safe_ver}"
311
+
312
+ action = FixAction(
313
+ file=filepath,
314
+ line=line,
315
+ description=f"升级 {package} 至安全版本({safe_ver})",
316
+ original_code=original_line,
317
+ fixed_code=fixed_line,
318
+ category="dependency",
319
+ confidence=0.9,
320
+ )
321
+
322
+ return FixResult(
323
+ file=filepath,
324
+ actions=[action],
325
+ genome_review_passed=True,
326
+ )
327
+
328
+ def get_results(self) -> List[FixResult]:
329
+ return self._results