Skill 代码审查
本项目构建一个代码审查 Skill,用户上传代码文件后,自动分析代码质量、风格问题和潜在 Bug。
目标:掌握工具链集成与结构化问题报告的生成。
项目需求分析
| 功能 | 支持语言 | 工具 |
|---|---|---|
| 语法检查 | Python | py_compile(内置) |
| 代码风格检查 | Python | flake8 |
| 代码复杂度分析 | Python | radon |
| JavaScript 检查 | JS/TS | eslint(npx) |
| 综合问题报告 | 所有 | 自定义聚合脚本 |
目录结构
code-reviewer/
├── SKILL.md
└── scripts/
├── requirements.txt
├── check_python.py
├── check_js.py
└── generate_report.py
Python 代码检查脚本
实例
# 文件路径:scripts/check_python.py
import subprocess
import sys
import json
import os
def ensure_tools():
"""确保检查工具已安装"""
for pkg in ["flake8", "radon"]:
try:
__import__(pkg.replace("-", "_"))
except ImportError:
subprocess.run([sys.executable, "-m", "pip", "install", pkg,
"--break-system-packages", "--quiet"])
def check_syntax(file_path: str) -> dict:
"""检查 Python 语法"""
result = subprocess.run(
[sys.executable, "-m", "py_compile", file_path],
capture_output=True, text=True
)
if result.returncode == 0:
return {"passed": True, "errors": []}
return {
"passed": False,
"errors": [result.stderr.strip()]
}
def check_style(file_path: str) -> list:
"""运行 flake8 检查代码风格,返回问题列表"""
result = subprocess.run(
["python", "-m", "flake8", file_path,
"--max-line-length=100",
"--format=%(row)d:%(col)d %(code)s %(text)s"],
capture_output=True, text=True
)
issues = []
for line in result.stdout.strip().splitlines():
parts = line.split(" ", 2)
if len(parts) >= 3:
loc, code, text = parts
row, col = loc.split(":")
issues.append({
"line": int(row), "col": int(col),
"code": code, "message": text
})
return issues
def check_complexity(file_path: str) -> list:
"""用 radon 分析函数复杂度,返回复杂度高的函数"""
result = subprocess.run(
["python", "-m", "radon", "cc", file_path, "-s", "--json"],
capture_output=True, text=True
)
try:
data = json.loads(result.stdout)
functions = data.get(file_path, [])
# 只返回复杂度等级 C 以上(复杂度 > 10)的函数
return [
{"name": f["name"], "complexity": f["complexity"],
"rank": f["rank"], "line": f["lineno"]}
for f in functions
if f.get("rank", "A") in ("C", "D", "E", "F")
]
except Exception:
return []
def review_python(file_path: str) -> dict:
"""执行完整的 Python 代码审查"""
if not os.path.exists(file_path):
return {"status": "error", "message": f"文件不存在:{file_path}"}
ensure_tools()
syntax = check_syntax(file_path)
style = check_style(file_path) if syntax["passed"] else []
complexity = check_complexity(file_path) if syntax["passed"] else []
# 计算总体评级
error_count = len([i for i in style if i["code"].startswith("E")])
warning_count = len([i for i in style if i["code"].startswith("W")])
if not syntax["passed"]:
grade = "F"
elif error_count > 10:
grade = "C"
elif error_count > 3:
grade = "B"
else:
grade = "A"
return {
"status": "success",
"file": os.path.basename(file_path),
"grade": grade,
"syntax_ok": syntax["passed"],
"syntax_errors": syntax["errors"],
"style_issues": style[:20], # 最多展示 20 条
"style_count": len(style),
"error_count": error_count,
"warning_count": warning_count,
"complex_functions": complexity
}
if __name__ == "__main__":
result = review_python(sys.argv[1] if len(sys.argv) > 1 else "")
print(json.dumps(result, ensure_ascii=False, indent=2))
import subprocess
import sys
import json
import os
def ensure_tools():
"""确保检查工具已安装"""
for pkg in ["flake8", "radon"]:
try:
__import__(pkg.replace("-", "_"))
except ImportError:
subprocess.run([sys.executable, "-m", "pip", "install", pkg,
"--break-system-packages", "--quiet"])
def check_syntax(file_path: str) -> dict:
"""检查 Python 语法"""
result = subprocess.run(
[sys.executable, "-m", "py_compile", file_path],
capture_output=True, text=True
)
if result.returncode == 0:
return {"passed": True, "errors": []}
return {
"passed": False,
"errors": [result.stderr.strip()]
}
def check_style(file_path: str) -> list:
"""运行 flake8 检查代码风格,返回问题列表"""
result = subprocess.run(
["python", "-m", "flake8", file_path,
"--max-line-length=100",
"--format=%(row)d:%(col)d %(code)s %(text)s"],
capture_output=True, text=True
)
issues = []
for line in result.stdout.strip().splitlines():
parts = line.split(" ", 2)
if len(parts) >= 3:
loc, code, text = parts
row, col = loc.split(":")
issues.append({
"line": int(row), "col": int(col),
"code": code, "message": text
})
return issues
def check_complexity(file_path: str) -> list:
"""用 radon 分析函数复杂度,返回复杂度高的函数"""
result = subprocess.run(
["python", "-m", "radon", "cc", file_path, "-s", "--json"],
capture_output=True, text=True
)
try:
data = json.loads(result.stdout)
functions = data.get(file_path, [])
# 只返回复杂度等级 C 以上(复杂度 > 10)的函数
return [
{"name": f["name"], "complexity": f["complexity"],
"rank": f["rank"], "line": f["lineno"]}
for f in functions
if f.get("rank", "A") in ("C", "D", "E", "F")
]
except Exception:
return []
def review_python(file_path: str) -> dict:
"""执行完整的 Python 代码审查"""
if not os.path.exists(file_path):
return {"status": "error", "message": f"文件不存在:{file_path}"}
ensure_tools()
syntax = check_syntax(file_path)
style = check_style(file_path) if syntax["passed"] else []
complexity = check_complexity(file_path) if syntax["passed"] else []
# 计算总体评级
error_count = len([i for i in style if i["code"].startswith("E")])
warning_count = len([i for i in style if i["code"].startswith("W")])
if not syntax["passed"]:
grade = "F"
elif error_count > 10:
grade = "C"
elif error_count > 3:
grade = "B"
else:
grade = "A"
return {
"status": "success",
"file": os.path.basename(file_path),
"grade": grade,
"syntax_ok": syntax["passed"],
"syntax_errors": syntax["errors"],
"style_issues": style[:20], # 最多展示 20 条
"style_count": len(style),
"error_count": error_count,
"warning_count": warning_count,
"complex_functions": complexity
}
if __name__ == "__main__":
result = review_python(sys.argv[1] if len(sys.argv) > 1 else "")
print(json.dumps(result, ensure_ascii=False, indent=2))
输出:
{
"status": "success",
"file": "runoob_app.py",
"grade": "B",
"syntax_ok": true,
"style_issues": [
{"line": 12, "col": 1, "code": "E302", "message": "expected 2 blank lines"},
{"line": 34, "col": 80, "code": "E501", "message": "line too long (103 > 100)"}
],
"style_count": 5,
"error_count": 3,
"warning_count": 2,
"complex_functions": []
}
SKILL.md 完整内容
--- name: code-reviewer version: 1.0.0 description: > 自动审查用户上传的代码文件,检查语法错误、风格问题和代码复杂度, 生成带评级的问题报告并给出改进建议。当用户需要代码审查、 检查代码质量、查找 Bug、评估代码规范时触发。 --- # 代码审查助手 ## 执行流程 ### 第一步:识别代码文件 获取用户上传的代码文件路径,识别编程语言(根据扩展名): - .py → Python 审查 - .js / .ts → JavaScript/TypeScript 审查 - 其他格式 → 告知暂不支持,列出支持的格式 ### 第二步:运行自动化检查 Python 文件运行: ```bash cd scripts/ python check_python.py <文件路径> ``` ### 第三步:生成并解读报告 根据 JSON 输出,以人性化方式呈现结果: 1. **总体评级**:A(优秀)/ B(良好)/ C(需改进)/ F(语法错误) 2. **问题汇总表格**:行号、问题代码、描述 3. **Top 3 优先修复项**:选出最严重的 3 个问题,给出修复建议和示例 若文件无问题,给出简短正面评价: > 代码检查通过!共扫描 X 行,未发现语法错误和明显风格问题。
测试用例
实例
# 文件路径:scripts/tests/test_sample.py(用于测试审查 Skill 的样本文件)
# 故意包含一些常见问题,用于验证审查功能
import os,sys # E401: 多个 import 在同一行(风格问题)
import json
def processData(data): # N802: 函数名应为 snake_case
x = 1 # F841: 局部变量 x 赋值后未使用
result=[] # E225: 赋值号周围缺少空格
for i in data:
if i > 0:
result.append(i)
elif i == 0:
pass
else:
result.append(-i)
return result
class myClass: # N801: 类名应为 CamelCase
pass
# 故意包含一些常见问题,用于验证审查功能
import os,sys # E401: 多个 import 在同一行(风格问题)
import json
def processData(data): # N802: 函数名应为 snake_case
x = 1 # F841: 局部变量 x 赋值后未使用
result=[] # E225: 赋值号周围缺少空格
for i in data:
if i > 0:
result.append(i)
elif i == 0:
pass
else:
result.append(-i)
return result
class myClass: # N801: 类名应为 CamelCase
pass
运行审查:
cd scripts/ python check_python.py tests/test_sample.py
输出:
{
"status": "success",
"file": "test_sample.py",
"grade": "B",
"syntax_ok": true,
"style_count": 5,
"error_count": 4,
"warning_count": 1,
"complex_functions": []
}
