code-abyss 1.5.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.
- package/LICENSE +21 -0
- package/README.md +197 -0
- package/bin/install.js +193 -0
- package/bin/uninstall.js +42 -0
- package/config/AGENTS.md +247 -0
- package/config/CLAUDE.md +207 -0
- package/config/settings.example.json +27 -0
- package/output-styles/abyss-cultivator.md +399 -0
- package/package.json +41 -0
- package/skills/SKILL.md +115 -0
- package/skills/ai/SKILL.md +29 -0
- package/skills/ai/agent-dev.md +242 -0
- package/skills/ai/llm-security.md +288 -0
- package/skills/architecture/SKILL.md +41 -0
- package/skills/architecture/api-design.md +225 -0
- package/skills/architecture/caching.md +299 -0
- package/skills/architecture/cloud-native.md +285 -0
- package/skills/architecture/compliance.md +299 -0
- package/skills/architecture/data-security.md +184 -0
- package/skills/architecture/message-queue.md +329 -0
- package/skills/architecture/security-arch.md +210 -0
- package/skills/development/SKILL.md +43 -0
- package/skills/development/cpp.md +246 -0
- package/skills/development/go.md +323 -0
- package/skills/development/java.md +277 -0
- package/skills/development/python.md +288 -0
- package/skills/development/rust.md +313 -0
- package/skills/development/shell.md +313 -0
- package/skills/development/typescript.md +277 -0
- package/skills/devops/SKILL.md +36 -0
- package/skills/devops/cost-optimization.md +272 -0
- package/skills/devops/database.md +217 -0
- package/skills/devops/devsecops.md +198 -0
- package/skills/devops/git-workflow.md +181 -0
- package/skills/devops/observability.md +280 -0
- package/skills/devops/performance.md +273 -0
- package/skills/devops/testing.md +186 -0
- package/skills/gen-docs/SKILL.md +114 -0
- package/skills/gen-docs/scripts/doc_generator.py +491 -0
- package/skills/multi-agent/SKILL.md +268 -0
- package/skills/run_skill.py +88 -0
- package/skills/security/SKILL.md +51 -0
- package/skills/security/blue-team.md +379 -0
- package/skills/security/code-audit.md +265 -0
- package/skills/security/pentest.md +226 -0
- package/skills/security/red-team.md +321 -0
- package/skills/security/threat-intel.md +322 -0
- package/skills/security/vuln-research.md +369 -0
- package/skills/tests/README.md +225 -0
- package/skills/tests/SUMMARY.md +362 -0
- package/skills/tests/__init__.py +3 -0
- package/skills/tests/test_change_analyzer.py +558 -0
- package/skills/tests/test_doc_generator.py +538 -0
- package/skills/tests/test_module_scanner.py +376 -0
- package/skills/tests/test_quality_checker.py +516 -0
- package/skills/tests/test_security_scanner.py +426 -0
- package/skills/verify-change/SKILL.md +138 -0
- package/skills/verify-change/scripts/change_analyzer.py +529 -0
- package/skills/verify-module/SKILL.md +125 -0
- package/skills/verify-module/scripts/module_scanner.py +321 -0
- package/skills/verify-quality/SKILL.md +158 -0
- package/skills/verify-quality/scripts/quality_checker.py +481 -0
- package/skills/verify-security/SKILL.md +141 -0
- package/skills/verify-security/scripts/security_scanner.py +368 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
代码质量检查器
|
|
4
|
+
检测代码复杂度、重复代码、命名规范、函数长度等
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import sys
|
|
10
|
+
import json
|
|
11
|
+
import ast
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import List, Dict, Optional, Set
|
|
15
|
+
from enum import Enum
|
|
16
|
+
from collections import defaultdict
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Severity(Enum):
|
|
20
|
+
ERROR = "error"
|
|
21
|
+
WARNING = "warning"
|
|
22
|
+
INFO = "info"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class Issue:
|
|
27
|
+
severity: Severity
|
|
28
|
+
category: str
|
|
29
|
+
message: str
|
|
30
|
+
file_path: str
|
|
31
|
+
line_number: Optional[int] = None
|
|
32
|
+
suggestion: Optional[str] = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class FileMetrics:
|
|
37
|
+
path: str
|
|
38
|
+
lines: int = 0
|
|
39
|
+
code_lines: int = 0
|
|
40
|
+
comment_lines: int = 0
|
|
41
|
+
blank_lines: int = 0
|
|
42
|
+
functions: int = 0
|
|
43
|
+
classes: int = 0
|
|
44
|
+
max_complexity: int = 0
|
|
45
|
+
avg_function_length: float = 0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class QualityResult:
|
|
50
|
+
scan_path: str
|
|
51
|
+
files_scanned: int = 0
|
|
52
|
+
total_lines: int = 0
|
|
53
|
+
total_code_lines: int = 0
|
|
54
|
+
issues: List[Issue] = field(default_factory=list)
|
|
55
|
+
file_metrics: List[FileMetrics] = field(default_factory=list)
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def passed(self) -> bool:
|
|
59
|
+
return not any(i.severity == Severity.ERROR for i in self.issues)
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def error_count(self) -> int:
|
|
63
|
+
return sum(1 for i in self.issues if i.severity == Severity.ERROR)
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def warning_count(self) -> int:
|
|
67
|
+
return sum(1 for i in self.issues if i.severity == Severity.WARNING)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# 质量规则配置
|
|
71
|
+
MAX_LINE_LENGTH = 120
|
|
72
|
+
MAX_FUNCTION_LENGTH = 50
|
|
73
|
+
MAX_FILE_LENGTH = 500
|
|
74
|
+
MAX_COMPLEXITY = 10
|
|
75
|
+
MAX_PARAMETERS = 5
|
|
76
|
+
MIN_FUNCTION_NAME_LENGTH = 2
|
|
77
|
+
MAX_FUNCTION_NAME_LENGTH = 40
|
|
78
|
+
PYTHON_SPECIAL_METHODS = {"setUp", "tearDown", "setUpClass", "tearDownClass", "setUpModule", "tearDownModule"}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class PythonAnalyzer(ast.NodeVisitor):
|
|
82
|
+
"""Python AST 分析器"""
|
|
83
|
+
|
|
84
|
+
def __init__(self, file_path: str, source: str):
|
|
85
|
+
self.file_path = file_path
|
|
86
|
+
self.source = source
|
|
87
|
+
self.lines = source.split('\n')
|
|
88
|
+
self.issues: List[Issue] = []
|
|
89
|
+
self.functions: List[Dict] = []
|
|
90
|
+
self.classes: List[Dict] = []
|
|
91
|
+
self.complexity = 0
|
|
92
|
+
|
|
93
|
+
def analyze(self) -> tuple[List[Issue], List[Dict], List[Dict], int]:
|
|
94
|
+
try:
|
|
95
|
+
tree = ast.parse(self.source)
|
|
96
|
+
self.visit(tree)
|
|
97
|
+
except SyntaxError as e:
|
|
98
|
+
self.issues.append(Issue(
|
|
99
|
+
severity=Severity.ERROR,
|
|
100
|
+
category="语法",
|
|
101
|
+
message=f"语法错误: {e.msg}",
|
|
102
|
+
file_path=self.file_path,
|
|
103
|
+
line_number=e.lineno
|
|
104
|
+
))
|
|
105
|
+
return self.issues, self.functions, self.classes, self.complexity
|
|
106
|
+
|
|
107
|
+
def visit_FunctionDef(self, node):
|
|
108
|
+
self._analyze_function(node)
|
|
109
|
+
self.generic_visit(node)
|
|
110
|
+
|
|
111
|
+
def visit_AsyncFunctionDef(self, node):
|
|
112
|
+
self._analyze_function(node)
|
|
113
|
+
self.generic_visit(node)
|
|
114
|
+
|
|
115
|
+
def visit_ClassDef(self, node):
|
|
116
|
+
self.classes.append({
|
|
117
|
+
"name": node.name,
|
|
118
|
+
"line": node.lineno,
|
|
119
|
+
"methods": len([n for n in node.body if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))])
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
# 检查类名
|
|
123
|
+
if not re.match(r'^[A-Z][a-zA-Z0-9]*$', node.name):
|
|
124
|
+
self.issues.append(Issue(
|
|
125
|
+
severity=Severity.WARNING,
|
|
126
|
+
category="命名",
|
|
127
|
+
message=f"类名 '{node.name}' 不符合 PascalCase 规范",
|
|
128
|
+
file_path=self.file_path,
|
|
129
|
+
line_number=node.lineno,
|
|
130
|
+
suggestion="类名应使用 PascalCase,如 MyClassName"
|
|
131
|
+
))
|
|
132
|
+
|
|
133
|
+
self.generic_visit(node)
|
|
134
|
+
|
|
135
|
+
def _analyze_function(self, node):
|
|
136
|
+
func_info = {
|
|
137
|
+
"name": node.name,
|
|
138
|
+
"line": node.lineno,
|
|
139
|
+
"length": self._get_function_length(node),
|
|
140
|
+
"complexity": self._calculate_complexity(node),
|
|
141
|
+
"parameters": len(node.args.args)
|
|
142
|
+
}
|
|
143
|
+
self.functions.append(func_info)
|
|
144
|
+
self.complexity = max(self.complexity, func_info["complexity"])
|
|
145
|
+
|
|
146
|
+
# 检查函数长度
|
|
147
|
+
if func_info["length"] > MAX_FUNCTION_LENGTH:
|
|
148
|
+
self.issues.append(Issue(
|
|
149
|
+
severity=Severity.WARNING,
|
|
150
|
+
category="复杂度",
|
|
151
|
+
message=f"函数 '{node.name}' 过长 ({func_info['length']} 行 > {MAX_FUNCTION_LENGTH})",
|
|
152
|
+
file_path=self.file_path,
|
|
153
|
+
line_number=node.lineno,
|
|
154
|
+
suggestion="考虑拆分为多个小函数"
|
|
155
|
+
))
|
|
156
|
+
|
|
157
|
+
# 检查复杂度
|
|
158
|
+
if func_info["complexity"] > MAX_COMPLEXITY:
|
|
159
|
+
self.issues.append(Issue(
|
|
160
|
+
severity=Severity.WARNING,
|
|
161
|
+
category="复杂度",
|
|
162
|
+
message=f"函数 '{node.name}' 圈复杂度过高 ({func_info['complexity']} > {MAX_COMPLEXITY})",
|
|
163
|
+
file_path=self.file_path,
|
|
164
|
+
line_number=node.lineno,
|
|
165
|
+
suggestion="减少嵌套层级,提取子函数"
|
|
166
|
+
))
|
|
167
|
+
|
|
168
|
+
# 检查参数数量
|
|
169
|
+
if func_info["parameters"] > MAX_PARAMETERS:
|
|
170
|
+
self.issues.append(Issue(
|
|
171
|
+
severity=Severity.WARNING,
|
|
172
|
+
category="设计",
|
|
173
|
+
message=f"函数 '{node.name}' 参数过多 ({func_info['parameters']} > {MAX_PARAMETERS})",
|
|
174
|
+
file_path=self.file_path,
|
|
175
|
+
line_number=node.lineno,
|
|
176
|
+
suggestion="考虑使用配置对象或数据类封装参数"
|
|
177
|
+
))
|
|
178
|
+
|
|
179
|
+
# 检查函数命名
|
|
180
|
+
if not node.name.startswith('_') and node.name not in PYTHON_SPECIAL_METHODS and not node.name.startswith('visit_'):
|
|
181
|
+
if not re.match(r'^[a-z][a-z0-9_]*$', node.name):
|
|
182
|
+
self.issues.append(Issue(
|
|
183
|
+
severity=Severity.INFO,
|
|
184
|
+
category="命名",
|
|
185
|
+
message=f"函数名 '{node.name}' 不符合 snake_case 规范",
|
|
186
|
+
file_path=self.file_path,
|
|
187
|
+
line_number=node.lineno,
|
|
188
|
+
suggestion="函数名应使用 snake_case,如 my_function_name"
|
|
189
|
+
))
|
|
190
|
+
|
|
191
|
+
if len(node.name) < MIN_FUNCTION_NAME_LENGTH:
|
|
192
|
+
self.issues.append(Issue(
|
|
193
|
+
severity=Severity.WARNING,
|
|
194
|
+
category="命名",
|
|
195
|
+
message=f"函数名 '{node.name}' 过短",
|
|
196
|
+
file_path=self.file_path,
|
|
197
|
+
line_number=node.lineno,
|
|
198
|
+
suggestion="使用更具描述性的函数名"
|
|
199
|
+
))
|
|
200
|
+
|
|
201
|
+
def _get_function_length(self, node) -> int:
|
|
202
|
+
if hasattr(node, 'end_lineno'):
|
|
203
|
+
return node.end_lineno - node.lineno + 1
|
|
204
|
+
return len(ast.unparse(node).split('\n'))
|
|
205
|
+
|
|
206
|
+
def _calculate_complexity(self, node) -> int:
|
|
207
|
+
"""计算圈复杂度"""
|
|
208
|
+
complexity = 1
|
|
209
|
+
|
|
210
|
+
for child in ast.walk(node):
|
|
211
|
+
if isinstance(child, (ast.If, ast.While, ast.For, ast.AsyncFor)):
|
|
212
|
+
complexity += 1
|
|
213
|
+
elif isinstance(child, ast.ExceptHandler):
|
|
214
|
+
complexity += 1
|
|
215
|
+
elif isinstance(child, (ast.And, ast.Or)):
|
|
216
|
+
complexity += 1
|
|
217
|
+
elif isinstance(child, ast.comprehension):
|
|
218
|
+
complexity += 1
|
|
219
|
+
if child.ifs:
|
|
220
|
+
complexity += len(child.ifs)
|
|
221
|
+
|
|
222
|
+
return complexity
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def analyze_python_file(file_path: Path) -> tuple[FileMetrics, List[Issue]]:
|
|
226
|
+
"""分析 Python 文件"""
|
|
227
|
+
metrics = FileMetrics(path=str(file_path))
|
|
228
|
+
issues = []
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
content = file_path.read_text(encoding='utf-8', errors='ignore')
|
|
232
|
+
lines = content.split('\n')
|
|
233
|
+
except Exception as e:
|
|
234
|
+
issues.append(Issue(
|
|
235
|
+
severity=Severity.ERROR,
|
|
236
|
+
category="文件",
|
|
237
|
+
message=f"无法读取文件: {e}",
|
|
238
|
+
file_path=str(file_path)
|
|
239
|
+
))
|
|
240
|
+
return metrics, issues
|
|
241
|
+
|
|
242
|
+
# 基础行数统计
|
|
243
|
+
metrics.lines = len(lines)
|
|
244
|
+
in_multiline_string = False
|
|
245
|
+
|
|
246
|
+
for i, line in enumerate(lines, 1):
|
|
247
|
+
stripped = line.strip()
|
|
248
|
+
|
|
249
|
+
if not stripped:
|
|
250
|
+
metrics.blank_lines += 1
|
|
251
|
+
elif stripped.startswith('#'):
|
|
252
|
+
metrics.comment_lines += 1
|
|
253
|
+
elif '"""' in stripped or "'''" in stripped:
|
|
254
|
+
if stripped.count('"""') == 2 or stripped.count("'''") == 2:
|
|
255
|
+
metrics.comment_lines += 1
|
|
256
|
+
else:
|
|
257
|
+
in_multiline_string = not in_multiline_string
|
|
258
|
+
metrics.comment_lines += 1
|
|
259
|
+
elif in_multiline_string:
|
|
260
|
+
metrics.comment_lines += 1
|
|
261
|
+
else:
|
|
262
|
+
metrics.code_lines += 1
|
|
263
|
+
|
|
264
|
+
# 检查行长度
|
|
265
|
+
if len(line) > MAX_LINE_LENGTH:
|
|
266
|
+
issues.append(Issue(
|
|
267
|
+
severity=Severity.INFO,
|
|
268
|
+
category="格式",
|
|
269
|
+
message=f"行过长 ({len(line)} > {MAX_LINE_LENGTH})",
|
|
270
|
+
file_path=str(file_path),
|
|
271
|
+
line_number=i
|
|
272
|
+
))
|
|
273
|
+
|
|
274
|
+
# 检查文件长度
|
|
275
|
+
if metrics.code_lines > MAX_FILE_LENGTH:
|
|
276
|
+
issues.append(Issue(
|
|
277
|
+
severity=Severity.WARNING,
|
|
278
|
+
category="复杂度",
|
|
279
|
+
message=f"文件过长 ({metrics.code_lines} 行代码 > {MAX_FILE_LENGTH})",
|
|
280
|
+
file_path=str(file_path),
|
|
281
|
+
suggestion="考虑拆分为多个模块"
|
|
282
|
+
))
|
|
283
|
+
|
|
284
|
+
# AST 分析
|
|
285
|
+
analyzer = PythonAnalyzer(str(file_path), content)
|
|
286
|
+
ast_issues, functions, classes, complexity = analyzer.analyze()
|
|
287
|
+
issues.extend(ast_issues)
|
|
288
|
+
|
|
289
|
+
metrics.functions = len(functions)
|
|
290
|
+
metrics.classes = len(classes)
|
|
291
|
+
metrics.max_complexity = complexity
|
|
292
|
+
|
|
293
|
+
if functions:
|
|
294
|
+
metrics.avg_function_length = sum(f["length"] for f in functions) / len(functions)
|
|
295
|
+
|
|
296
|
+
return metrics, issues
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def analyze_generic_file(file_path: Path) -> tuple[FileMetrics, List[Issue]]:
|
|
300
|
+
"""分析通用代码文件"""
|
|
301
|
+
metrics = FileMetrics(path=str(file_path))
|
|
302
|
+
issues = []
|
|
303
|
+
|
|
304
|
+
try:
|
|
305
|
+
content = file_path.read_text(encoding='utf-8', errors='ignore')
|
|
306
|
+
lines = content.split('\n')
|
|
307
|
+
except Exception:
|
|
308
|
+
return metrics, issues
|
|
309
|
+
|
|
310
|
+
metrics.lines = len(lines)
|
|
311
|
+
|
|
312
|
+
comment_patterns = {
|
|
313
|
+
'.js': '//',
|
|
314
|
+
'.ts': '//',
|
|
315
|
+
'.go': '//',
|
|
316
|
+
'.java': '//',
|
|
317
|
+
'.c': '//',
|
|
318
|
+
'.cpp': '//',
|
|
319
|
+
'.rs': '//',
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
suffix = file_path.suffix.lower()
|
|
323
|
+
comment_prefix = comment_patterns.get(suffix, '//')
|
|
324
|
+
|
|
325
|
+
for i, line in enumerate(lines, 1):
|
|
326
|
+
stripped = line.strip()
|
|
327
|
+
|
|
328
|
+
if not stripped:
|
|
329
|
+
metrics.blank_lines += 1
|
|
330
|
+
elif stripped.startswith(comment_prefix) or stripped.startswith('/*') or stripped.startswith('*'):
|
|
331
|
+
metrics.comment_lines += 1
|
|
332
|
+
else:
|
|
333
|
+
metrics.code_lines += 1
|
|
334
|
+
|
|
335
|
+
if len(line) > MAX_LINE_LENGTH:
|
|
336
|
+
issues.append(Issue(
|
|
337
|
+
severity=Severity.INFO,
|
|
338
|
+
category="格式",
|
|
339
|
+
message=f"行过长 ({len(line)} > {MAX_LINE_LENGTH})",
|
|
340
|
+
file_path=str(file_path),
|
|
341
|
+
line_number=i
|
|
342
|
+
))
|
|
343
|
+
|
|
344
|
+
if metrics.code_lines > MAX_FILE_LENGTH:
|
|
345
|
+
issues.append(Issue(
|
|
346
|
+
severity=Severity.WARNING,
|
|
347
|
+
category="复杂度",
|
|
348
|
+
message=f"文件过长 ({metrics.code_lines} 行代码 > {MAX_FILE_LENGTH})",
|
|
349
|
+
file_path=str(file_path),
|
|
350
|
+
suggestion="考虑拆分为多个模块"
|
|
351
|
+
))
|
|
352
|
+
|
|
353
|
+
return metrics, issues
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def scan_directory(path: str, exclude_dirs: List[str] = None) -> QualityResult:
|
|
357
|
+
"""扫描目录"""
|
|
358
|
+
scan_path = Path(path).resolve()
|
|
359
|
+
result = QualityResult(scan_path=str(scan_path))
|
|
360
|
+
|
|
361
|
+
if exclude_dirs is None:
|
|
362
|
+
exclude_dirs = ['.git', 'node_modules', '__pycache__', '.venv', 'venv', 'dist', 'build', '.tox']
|
|
363
|
+
|
|
364
|
+
code_extensions = {'.py', '.js', '.ts', '.go', '.java', '.rs', '.c', '.cpp'}
|
|
365
|
+
|
|
366
|
+
for file_path in scan_path.rglob('*'):
|
|
367
|
+
if any(ex in file_path.parts for ex in exclude_dirs):
|
|
368
|
+
continue
|
|
369
|
+
|
|
370
|
+
if file_path.is_file() and file_path.suffix.lower() in code_extensions:
|
|
371
|
+
result.files_scanned += 1
|
|
372
|
+
|
|
373
|
+
if file_path.suffix.lower() == '.py':
|
|
374
|
+
metrics, issues = analyze_python_file(file_path)
|
|
375
|
+
else:
|
|
376
|
+
metrics, issues = analyze_generic_file(file_path)
|
|
377
|
+
|
|
378
|
+
result.file_metrics.append(metrics)
|
|
379
|
+
result.issues.extend(issues)
|
|
380
|
+
result.total_lines += metrics.lines
|
|
381
|
+
result.total_code_lines += metrics.code_lines
|
|
382
|
+
|
|
383
|
+
return result
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def format_report(result: QualityResult, verbose: bool = False) -> str:
|
|
387
|
+
"""格式化报告"""
|
|
388
|
+
lines = []
|
|
389
|
+
lines.append("=" * 60)
|
|
390
|
+
lines.append("代码质量检查报告")
|
|
391
|
+
lines.append("=" * 60)
|
|
392
|
+
|
|
393
|
+
lines.append(f"\n扫描路径: {result.scan_path}")
|
|
394
|
+
lines.append(f"扫描文件: {result.files_scanned}")
|
|
395
|
+
lines.append(f"总行数: {result.total_lines}")
|
|
396
|
+
lines.append(f"代码行数: {result.total_code_lines}")
|
|
397
|
+
lines.append(f"检查结果: {'✓ 通过' if result.passed else '✗ 需要关注'}")
|
|
398
|
+
lines.append(f"错误: {result.error_count} | 警告: {result.warning_count}")
|
|
399
|
+
|
|
400
|
+
if result.issues:
|
|
401
|
+
lines.append("\n" + "-" * 40)
|
|
402
|
+
lines.append("问题列表:")
|
|
403
|
+
lines.append("-" * 40)
|
|
404
|
+
|
|
405
|
+
# 按类别分组
|
|
406
|
+
by_category = defaultdict(list)
|
|
407
|
+
for issue in result.issues:
|
|
408
|
+
by_category[issue.category].append(issue)
|
|
409
|
+
|
|
410
|
+
severity_icons = {"error": "✗", "warning": "⚠", "info": "ℹ"}
|
|
411
|
+
|
|
412
|
+
for category, issues in sorted(by_category.items()):
|
|
413
|
+
lines.append(f"\n【{category}】({len(issues)} 个)")
|
|
414
|
+
for issue in issues[:10]: # 每类最多显示 10 个
|
|
415
|
+
icon = severity_icons[issue.severity.value]
|
|
416
|
+
loc = f":{issue.line_number}" if issue.line_number else ""
|
|
417
|
+
lines.append(f" {icon} {issue.file_path}{loc}")
|
|
418
|
+
lines.append(f" {issue.message}")
|
|
419
|
+
if verbose and issue.suggestion:
|
|
420
|
+
lines.append(f" 💡 {issue.suggestion}")
|
|
421
|
+
|
|
422
|
+
if len(issues) > 10:
|
|
423
|
+
lines.append(f" ... 及其他 {len(issues) - 10} 个问题")
|
|
424
|
+
|
|
425
|
+
if verbose and result.file_metrics:
|
|
426
|
+
# 找出最复杂的文件
|
|
427
|
+
complex_files = sorted(result.file_metrics, key=lambda m: m.max_complexity, reverse=True)[:5]
|
|
428
|
+
if complex_files and complex_files[0].max_complexity > 0:
|
|
429
|
+
lines.append("\n" + "-" * 40)
|
|
430
|
+
lines.append("复杂度最高的文件:")
|
|
431
|
+
lines.append("-" * 40)
|
|
432
|
+
for m in complex_files:
|
|
433
|
+
if m.max_complexity > 0:
|
|
434
|
+
lines.append(f" {m.path}: 复杂度 {m.max_complexity}, {m.functions} 个函数")
|
|
435
|
+
|
|
436
|
+
lines.append("\n" + "=" * 60)
|
|
437
|
+
return "\n".join(lines)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def main():
|
|
441
|
+
import argparse
|
|
442
|
+
|
|
443
|
+
parser = argparse.ArgumentParser(description="代码质量检查器")
|
|
444
|
+
parser.add_argument("path", nargs="?", default=".", help="扫描路径")
|
|
445
|
+
parser.add_argument("-v", "--verbose", action="store_true", help="详细输出")
|
|
446
|
+
parser.add_argument("--json", action="store_true", help="JSON 格式输出")
|
|
447
|
+
|
|
448
|
+
args = parser.parse_args()
|
|
449
|
+
|
|
450
|
+
result = scan_directory(args.path)
|
|
451
|
+
|
|
452
|
+
if args.json:
|
|
453
|
+
output = {
|
|
454
|
+
"scan_path": result.scan_path,
|
|
455
|
+
"files_scanned": result.files_scanned,
|
|
456
|
+
"total_lines": result.total_lines,
|
|
457
|
+
"total_code_lines": result.total_code_lines,
|
|
458
|
+
"passed": result.passed,
|
|
459
|
+
"error_count": result.error_count,
|
|
460
|
+
"warning_count": result.warning_count,
|
|
461
|
+
"issues": [
|
|
462
|
+
{
|
|
463
|
+
"severity": i.severity.value,
|
|
464
|
+
"category": i.category,
|
|
465
|
+
"message": i.message,
|
|
466
|
+
"file_path": i.file_path,
|
|
467
|
+
"line_number": i.line_number,
|
|
468
|
+
"suggestion": i.suggestion
|
|
469
|
+
}
|
|
470
|
+
for i in result.issues
|
|
471
|
+
]
|
|
472
|
+
}
|
|
473
|
+
print(json.dumps(output, ensure_ascii=False, indent=2))
|
|
474
|
+
else:
|
|
475
|
+
print(format_report(result, args.verbose))
|
|
476
|
+
|
|
477
|
+
sys.exit(0 if result.passed else 1)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
if __name__ == "__main__":
|
|
481
|
+
main()
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: verify-security
|
|
3
|
+
description: 安全校验关卡。自动扫描代码安全漏洞,检测危险模式,确保安全决策有文档记录。当魔尊提到安全扫描、漏洞检测、安全审计、代码安全、OWASP、注入检测、敏感信息泄露时使用。在新建模块、安全相关变更、攻防任务、重构完成时自动触发。
|
|
4
|
+
user-invocable: true
|
|
5
|
+
disable-model-invocation: false
|
|
6
|
+
allowed-tools: Bash, Read, Grep
|
|
7
|
+
argument-hint: <扫描路径>
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# ⚖ 校验关卡 · 安全校验
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## 核心原则
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
安全即道基,破则劫败
|
|
17
|
+
安全决策必须可追溯
|
|
18
|
+
Critical/High 问题必须修复后才能交付
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 自动扫描
|
|
22
|
+
|
|
23
|
+
运行安全扫描脚本(跨平台):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# 在 skill 目录下运行
|
|
27
|
+
python scripts/security_scanner.py <扫描路径>
|
|
28
|
+
python scripts/security_scanner.py <扫描路径> -v # 详细模式
|
|
29
|
+
python scripts/security_scanner.py <扫描路径> --json # JSON 输出
|
|
30
|
+
python scripts/security_scanner.py <扫描路径> --exclude vendor # 排除目录
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 检测范围
|
|
34
|
+
|
|
35
|
+
### 自动检测的漏洞类型
|
|
36
|
+
|
|
37
|
+
| 类别 | 检测项 | 严重度 |
|
|
38
|
+
|------|--------|--------|
|
|
39
|
+
| **注入** | SQL 注入、命令注入、代码注入 | 🔴 Critical |
|
|
40
|
+
| **敏感信息** | 硬编码密钥、AWS Key、私钥 | 🔴 Critical |
|
|
41
|
+
| **XSS** | innerHTML、dangerouslySetInnerHTML | 🟠 High |
|
|
42
|
+
| **反序列化** | pickle.loads、yaml.load | 🟠 High |
|
|
43
|
+
| **路径遍历** | 未验证的文件路径操作 | 🟠 High |
|
|
44
|
+
| **SSRF** | 未验证的 URL 请求 | 🟠 High |
|
|
45
|
+
| **XXE** | 不安全的 XML 解析 | 🟠 High |
|
|
46
|
+
| **弱加密** | MD5、SHA1 用于安全场景 | 🟡 Medium |
|
|
47
|
+
| **不安全随机** | random 模块用于安全场景 | 🟡 Medium |
|
|
48
|
+
| **调试代码** | console.log、print、debugger | 🔵 Low |
|
|
49
|
+
|
|
50
|
+
### 文档层面检查
|
|
51
|
+
|
|
52
|
+
安全相关代码必须在 DESIGN.md 中记录:
|
|
53
|
+
|
|
54
|
+
- [ ] **威胁模型** — 防御哪些攻击
|
|
55
|
+
- [ ] **安全决策** — 为何选择此方案
|
|
56
|
+
- [ ] **安全边界** — 信任边界在哪里
|
|
57
|
+
- [ ] **已知风险** — 接受了哪些风险
|
|
58
|
+
|
|
59
|
+
## 危险模式速查
|
|
60
|
+
|
|
61
|
+
### Python
|
|
62
|
+
```python
|
|
63
|
+
# 🔴 危险 - 触犯道基
|
|
64
|
+
eval(), exec(), os.system()
|
|
65
|
+
subprocess(..., shell=True)
|
|
66
|
+
pickle.loads(), yaml.load()
|
|
67
|
+
cursor.execute(f"SELECT * FROM t WHERE id = {id}")
|
|
68
|
+
|
|
69
|
+
# ✅ 安全替代 - 道基稳固
|
|
70
|
+
ast.literal_eval()
|
|
71
|
+
subprocess([...], shell=False)
|
|
72
|
+
yaml.safe_load()
|
|
73
|
+
cursor.execute("SELECT * FROM t WHERE id = %s", (id,))
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### JavaScript
|
|
77
|
+
```javascript
|
|
78
|
+
// 🔴 危险 - 触犯道基
|
|
79
|
+
eval(), innerHTML, document.write()
|
|
80
|
+
new Function(userInput)
|
|
81
|
+
|
|
82
|
+
// ✅ 安全替代 - 道基稳固
|
|
83
|
+
JSON.parse(), textContent
|
|
84
|
+
模板引擎自动转义
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Go
|
|
88
|
+
```go
|
|
89
|
+
// 🔴 危险 - 触犯道基
|
|
90
|
+
exec.Command("sh", "-c", userInput)
|
|
91
|
+
template.HTML(userInput)
|
|
92
|
+
|
|
93
|
+
// ✅ 安全替代 - 道基稳固
|
|
94
|
+
exec.Command("cmd", args...)
|
|
95
|
+
html/template 自动转义
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 校验流程
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
1. 运行 security_scanner.py 自动扫描
|
|
102
|
+
2. 分析扫描结果,按严重度排序
|
|
103
|
+
3. 检查安全决策是否有文档记录
|
|
104
|
+
4. 输出安全校验报告
|
|
105
|
+
5. Critical/High 问题必须修复后才能交付
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 自动触发时机
|
|
109
|
+
|
|
110
|
+
| 场景 | 触发条件 |
|
|
111
|
+
|------|----------|
|
|
112
|
+
| 新建模块 | 模块创建完成时 |
|
|
113
|
+
| 安全相关变更 | 涉及认证、授权、加密、输入处理 |
|
|
114
|
+
| 攻防任务 | 红队/蓝队任务完成时 |
|
|
115
|
+
| 重构完成 | 重构任务完成时 |
|
|
116
|
+
| 提交前 | 代码提交前检查 |
|
|
117
|
+
|
|
118
|
+
## 校验报告格式
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
## 安全校验报告
|
|
122
|
+
|
|
123
|
+
✓ 通过 | ✗ 未通过
|
|
124
|
+
|
|
125
|
+
- 🔴 Critical: N
|
|
126
|
+
- 🟠 High: N
|
|
127
|
+
- 🟡 Medium: N
|
|
128
|
+
- 🔵 Low: N
|
|
129
|
+
|
|
130
|
+
### 发现问题
|
|
131
|
+
|
|
132
|
+
| 文件 | 行号 | 类型 | 严重度 | 描述 |
|
|
133
|
+
|------|------|------|--------|------|
|
|
134
|
+
| ... | ... | ... | ... | ... |
|
|
135
|
+
|
|
136
|
+
### 结论
|
|
137
|
+
|
|
138
|
+
可交付 / 需修复后交付
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|