{
  "metadata": {
    "id": "ch27",
    "title": "第27章：代码审查助手",
    "volume": "vol8",
    "volume_title": "实战案例集",
    "word_count": 1769,
    "difficulty": "advanced",
    "prerequisites": [
      "ch16"
    ],
    "key_concepts": [
      "需求分析与功能规划",
      "业务背景",
      "功能清单",
      "技术选型",
      "架构设计",
      "核心代码实现",
      "数据模型",
      "AST 分析器",
      "安全扫描分析器",
      "性能分析器",
      "代码审查协调 Agent",
      "Git 集成",
      "完整的 PR 审查流程",
      "测试",
      "经验总结"
    ],
    "learning_objectives": [],
    "estimated_tokens": 1061,
    "source_file": "vol8/ch27_代码审查助手.md"
  },
  "overview": "",
  "sections": [
    {
      "id": "27.1",
      "title": "27.1 需求分析与功能规划",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "27.1.1",
          "title": "27.1.1 业务背景",
          "content": "代码审查（Code Review）是软件工程中至关重要但极其耗时的环节：\n\n1. **效率瓶颈**：平均每个 PR 的审查时间 30-60 分钟，资深工程师每周 10+ 小时花在 Code Review 上\n2. **质量波动**：人工审查容易遗漏问题，尤其是安全漏洞和性能隐患\n3. **知识传递慢**：新人代码缺少建设性的改进建议\n4. **标准不统一**：不同审查者关注点不同，导致代码风格不一致\n\n我们需要一个代码审查助手，实现：\n\n- **自动化审查**：PR 提交后自动分析，在开发者等待人工 Review 时给出即时反馈\n- **多维度检查**：代码规范、安全漏洞、性能问题、最佳实践\n- **可操作建议**：不只是指出问题，还要给出修复方案和代码示例\n- **Git 深度集成**：无缝融入现有开发工作流"
        },
        {
          "id": "27.1.2",
          "title": "27.1.2 功能清单",
          "content": "| 功能 | 描述 | 优先级 |\n|------|------|--------|\n| AST 语法分析 | 解析代码结构，检测语法问题 | P0 |\n| 代码规范检查 | PEP8、命名规范、文档规范 | P0 |\n| 安全漏洞扫描 | SQL注入、XSS、硬编码密钥等 | P0 |\n| 性能问题检测 | N+1 查询、内存泄漏、不必要的复制 | P1 |\n| 复杂度分析 | 圈复杂度、认知复杂度 | P1 |\n| 修复建议生成 | 问题代码 + 修复代码对比 | P0 |\n| Git PR 集成 | 自动评论、状态检查 | P1 |\n| 审查报告生成 | HTML/PDF 格式的完整报告 | P2 |"
        },
        {
          "id": "27.1.3",
          "title": "27.1.3 技术选型",
          "content": "- **AST 解析**：Python `ast` 模块 + `tree-sitter`（多语言支持）\n- **安全扫描**：自建规则引擎 + Semgrep 集成\n- **LLM 分析**：GPT-4o 处理语义层面的代码理解\n- **Git 集成**：GitPython + GitHub/GitLab API\n\n---"
        }
      ]
    },
    {
      "id": "27.2",
      "title": "27.2 架构设计",
      "level": 2,
      "content": "---",
      "subsections": []
    },
    {
      "id": "27.3",
      "title": "27.3 核心代码实现",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "27.3.1",
          "title": "27.3.1 数据模型",
          "content": ""
        },
        {
          "id": "27.3.2",
          "title": "27.3.2 AST 分析器",
          "content": ""
        },
        {
          "id": "27.3.3",
          "title": "27.3.3 安全扫描分析器",
          "content": ""
        },
        {
          "id": "27.3.4",
          "title": "27.3.4 性能分析器",
          "content": ""
        },
        {
          "id": "27.3.5",
          "title": "27.3.5 代码审查协调 Agent",
          "content": ""
        },
        {
          "id": "27.3.6",
          "title": "27.3.6 Git 集成",
          "content": ""
        },
        {
          "id": "27.3.7",
          "title": "27.3.7 完整的 PR 审查流程",
          "content": "---"
        }
      ]
    },
    {
      "id": "27.4",
      "title": "27.4 测试",
      "level": 2,
      "content": "---",
      "subsections": []
    },
    {
      "id": "27.5",
      "title": "27.5 经验总结",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "27.5.1",
          "title": "27.5.1 关键设计决策",
          "content": "1. **静态分析 + LLM 双轨制**：静态分析快速且确定性强，适合规则明确的问题（安全漏洞、代码规范）；LLM 适合语义理解和上下文相关的建议。两者互补，先跑静态分析获取确定结果，再用 LLM 补充深度分析。\n\n2. **置信度标注**：每个问题都标注置信度。高置信度问题（如硬编码密码、eval()）可以直接阻断 CI 流水线；低置信度问题作为建议呈现。\n\n3. **可操作的修复建议**：只指出问题不够，必须给出修复代码。这大幅提高了开发者采纳建议的比例。"
        },
        {
          "id": "27.5.2",
          "title": "27.5.2 踩坑记录",
          "content": "- **AST 分析的局限性**：Python 的 AST 无法检测运行时问题（如动态属性访问、`__getattr__` 魔法方法）。需要配合 LLM 补充。\n- **正则扫描的误报**：简单正则匹配会产生大量误报（如变量名包含 \"password\" 但并非密码）。通过更精确的正则和上下文分析降低误报。\n- **大型仓库性能**：扫描 10 万行代码需要优化。使用多进程并行分析不同文件。"
        },
        {
          "id": "27.5.3",
          "title": "27.5.3 扩展方向",
          "content": "- **多语言支持**：集成 tree-sitter 支持 JavaScript/TypeScript/Go/Rust\n- **IDE 插件**：VSCode/JetBrains 插件实现实时审查\n- **CI/CD 集成**：GitHub Actions / GitLab CI 自动审查 PR\n- **学习型规则**：从团队的审查历史中学习新的检查规则\n\n---\n\n**本章小结**：代码审查助手展示了 Agent 技术在开发工具领域的巨大潜力。关键在于**静态分析的确定性**与**LLM 的理解力**的完美结合。通过分层架构（静态分析 → 规则匹配 → LLM 深度分析），我们构建了一个既快速又智能的审查系统。"
        }
      ]
    }
  ],
  "code_blocks": [
    {
      "id": "code-1",
      "language": "text",
      "description": "---",
      "code": "code-review-assistant/\n├── app/\n│   ├── main.py\n│   ├── config.py\n│   ├── analyzers/              # 分析器\n│   │   ├── ast_analyzer.py     # AST 分析\n│   │   ├── security_analyzer.py # 安全扫描\n│   │   ├── performance_analyzer.py # 性能检测\n│   │   ├── style_analyzer.py   # 风格检查\n│   │   └── complexity_analyzer.py # 复杂度分析\n│   ├── agents/                 # Agent\n│   │   ├── review_agent.py     # 审查协调 Agent\n│   │   └── fix_agent.py        # 修复建议 Agent\n│   ├── git/                    # Git 集成\n│   │   ├── git_client.py\n│   │   └── github_client.py\n│   ├── models/\n│   │   ├── issue.py            # 问题模型\n│   │   └── report.py           # 报告模型\n│   └── utils/\n│       └── llm_client.py\n├── tests/\n└── requirements.txt",
      "section_ref": "27.2",
      "runnable": false,
      "dependencies": []
    },
    {
      "id": "code-2",
      "language": "python",
      "description": "",
      "code": "# app/models/issue.py\n\"\"\"代码审查问题模型\"\"\"\n\nfrom dataclasses import dataclass, field\nfrom enum import Enum\nfrom typing import Optional\n\n\nclass Severity(str, Enum):\n    INFO = \"info\"\n    WARNING = \"warning\"\n    ERROR = \"error\"\n    CRITICAL = \"critical\"\n\n\nclass IssueCategory(str, Enum):\n    STYLE = \"style\"          # 代码风格\n    SECURITY = \"security\"    # 安全漏洞\n    PERFORMANCE = \"performance\" # 性能问题\n    COMPLEXITY = \"complexity\"  # 复杂度\n    BEST_PRACTICE = \"best_practice\" # 最佳实践\n    BUG_RISK = \"bug_risk\"    # 潜在 Bug\n\n\n@dataclass\nclass CodeLocation:\n    \"\"\"代码位置\"\"\"\n    file_path: str\n    line_start: int\n    line_end: int\n    column_start: int = 0\n    column_end: int = 0\n    \n    def __str__(self):\n        if self.line_start == self.line_end:\n            return f\"{self.file_path}:{self.line_start}\"\n        return f\"{self.file_path}:{self.line_start}-{self.line_end}\"\n\n\n@dataclass\nclass ReviewIssue:\n    \"\"\"审查问题\"\"\"\n    id: str\n    title: str\n    description: str\n    severity: Severity\n    category: IssueCategory\n    location: CodeLocation\n    code_snippet: str = \"\"\n    fix_suggestion: str = \"\"\n    fixed_code: str = \"\"\n    confidence: float = 0.0\n    cwe_id: Optional[str] = None  # CWE 漏洞编号\n    rule_id: Optional[str] = None # 规则编号",
      "section_ref": "27.3.1",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-3",
      "language": "python",
      "description": "",
      "code": "# app/analyzers/ast_analyzer.py\n\"\"\"AST 语法分析器\"\"\"\n\nimport ast\nfrom typing import List\nfrom app.models.issue import ReviewIssue, Severity, IssueCategory, CodeLocation\n\n\nclass ASTAnalyzer:\n    \"\"\"Python AST 分析器\"\"\"\n    \n    def analyze(self, file_path: str, source_code: str) -> List[ReviewIssue]:\n        \"\"\"分析 Python 源码\"\"\"\n        issues = []\n        \n        try:\n            tree = ast.parse(source_code, filename=file_path)\n        except SyntaxError as e:\n            issues.append(ReviewIssue(\n                id=f\"ast-syntax-{e.lineno}\",\n                title=\"语法错误\",\n                description=f\"Python 语法错误: {e.msg}\",\n                severity=Severity.ERROR,\n                category=IssueCategory.BUG_RISK,\n                location=CodeLocation(file_path, e.lineno or 1, e.lineno or 1),\n                code_snippet=self._get_line(source_code, e.lineno or 1),\n                confidence=1.0,\n            ))\n            return issues\n        \n        # 运行各类检查\n        issues.extend(self._check_unused_imports(tree, file_path, source_code))\n        issues.extend(self._check_bare_except(tree, file_path, source_code))\n        issues.extend(self._check_mutable_defaults(tree, file_path, source_code))\n        issues.extend(self._check_shadowing(tree, file_path, source_code))\n        issues.extend(self._check_unused_variables(tree, file_path, source_code))\n        \n        return issues\n    \n    def _get_line(self, source: str, lineno: int) -> str:\n        lines = source.split('\\n')\n        if 1 <= lineno <= len(lines):\n            return lines[lineno - 1].strip()\n        return \"\"\n    \n    def _check_unused_imports(self, tree, path, source) -> List[ReviewIssue]:\n        \"\"\"检查未使用的 import\"\"\"\n        issues = []\n        # 收集所有 import 名称\n        imported_names = set()\n        for node in ast.walk(tree):\n            if isinstance(node, ast.Import):\n                for alias in node.names:\n                    name = alias.asname or alias.name\n                    imported_names.add(name)\n            elif isinstance(node, ast.ImportFrom):\n                for alias in node.names:\n                    if alias.name != '*':\n                        name = alias.asname or alias.name\n                        imported_names.add(name)\n        \n        # 简化检查：如果 import 了但没在其他地方出现\n        source_lines = source.split('\\n')\n        for name in imported_names:\n            # 排除常见的 \"导入即使用\" 场景（如 __all__、类型注解等）\n            occurrences = sum(1 for line in source_lines if name in line)\n            if occurrences == 1:  # 只在 import 语句中出现\n                for i, line in enumerate(source_lines, 1):\n                    if name in line and ('import' in line or 'from' in line):\n                        issues.append(ReviewIssue(\n                            id=f\"ast-unused-import-{i}\",\n                            title=f\"未使用的导入: {name}\",\n                            description=f\"导入了 `{name}` 但未在代码中使用。\",\n                            severity=Severity.WARNING,\n                            category=IssueCategory.STYLE,\n                            location=CodeLocation(path, i, i),\n                            code_snippet=line.strip(),\n                            fix_suggestion=f\"删除未使用的 import 语句。\",\n                            fixed_code=\"\",  # 空行\n                            confidence=0.7,\n                        ))\n                        break\n        \n        return issues\n    \n    def _check_bare_except(self, tree, path, source) -> List[ReviewIssue]:\n        \"\"\"检查裸 except\"\"\"\n        issues = []\n        for node in ast.walk(tree):\n            if isinstance(node, ast.ExceptHandler):\n                if node.type is None:\n                    issues.append(ReviewIssue(\n                        id=f\"ast-bare-except-{node.lineno}\",\n                        title=\"裸 except 子句\",\n                        description=(\n                            \"使用裸 `except:` 会捕获所有异常，包括 \"\n                            \"KeyboardInterrupt 和 SystemExit，\"\n                            \"可能隐藏严重错误。\"),\n                        severity=Severity.WARNING,\n                        category=IssueCategory.BEST_PRACTICE,\n                        location=CodeLocation(path, node.lineno, node.lineno),\n                        code_snippet=self._get_line(source, node.lineno),\n                        fix_suggestion=\"使用 `except Exception:` 代替裸 except\",\n                        fixed_code=\"except Exception as e:\",\n                        confidence=0.95,\n                    ))\n        return issues\n    \n    def _check_mutable_defaults(self, tree, path, source) -> List[ReviewIssue]:\n        \"\"\"检查可变默认参数\"\"\"\n        issues = []\n        mutable_types = (ast.List, ast.Dict, ast.Set)\n        \n        for node in ast.walk(tree):\n            if isinstance(node, ast.FunctionDef):\n                for default in node.args.defaults + node.args.kw_defaults:\n                    if default and isinstance(default, mutable_types):\n                        issues.append(ReviewIssue(\n                            id=f\"ast-mutable-default-{node.lineno}\",\n                            title=f\"可变默认参数: {node.name}\",\n                            description=(\n                                f\"函数 `{node.name}` 使用了可变对象作为默认参数。\"\n                                f\"Python 中可变默认参数在函数定义时创建一次，\"\n                                f\"所有调用共享同一个对象，会导致意外的状态共享。\"),\n                            severity=Severity.WARNING,\n                            category=IssueCategory.BUG_RISK,\n                            location=CodeLocation(path, node.lineno, node.lineno),\n                            code_snippet=self._get_line(source, node.lineno),\n                            fix_suggestion=\"使用 None 作为默认值，在函数体内创建\",\n                            fixed_code=(\n                                f\"def {node.name}(..., param=None):\\n\"\n                                f\"    if param is None:\\n\"\n                                f\"        param = []  # 或 {}\"\n                            ),\n                            confidence=0.95,\n                        ))\n        return issues\n    \n    def _check_shadowing(self, tree, path, source) -> List[ReviewIssue]:\n        \"\"\"检查变量名覆盖内置名称\"\"\"\n        issues = []\n        builtins = {\n            'list', 'dict', 'set', 'str', 'int', 'float', 'bool',\n            'type', 'id', 'input', 'print', 'range', 'len', 'max',\n            'min', 'sum', 'open', 'file', 'dir', 'vars', 'hash',\n            'object', 'super', 'property', 'staticmethod', 'classmethod',\n        }\n        \n        for node in ast.walk(tree):\n            if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):\n                for arg in node.args.args:\n                    if arg.arg in builtins:\n                        issues.append(ReviewIssue(\n                            id=f\"ast-shadow-{node.lineno}\",\n                            title=f\"覆盖内置名称: {arg.arg}\",\n                            description=(\n                                f\"参数名 `{arg.arg}` 覆盖了 Python 内置函数/类型。\"\n                                f\"这会导致在该函数作用域内无法使用原始的 `{arg.arg}`。\"),\n                            severity=Severity.INFO,\n                            category=IssueCategory.BEST_PRACTICE,\n                            location=CodeLocation(path, node.lineno, node.lineno),\n                            code_snippet=self._get_line(source, node.lineno),\n                            fix_suggestion=f\"将参数名改为 `{arg.arg}_` 或更具描述性的名称\",\n                            confidence=0.8,\n                        ))\n            \n            elif isinstance(node, ast.Assign):\n                for target in node.targets:\n                    if isinstance(target, ast.Name) and target.id in builtins:\n                        issues.append(ReviewIssue(\n                            id=f\"ast-shadow-assign-{node.lineno}\",\n                            title=f\"覆盖内置名称: {target.id}\",\n                            description=f\"变量 `{target.id}` 覆盖了内置名称。\",\n                            severity=Severity.INFO,\n                            category=IssueCategory.BEST_PRACTICE,\n                            location=CodeLocation(path, node.lineno, node.lineno),\n                            code_snippet=self._get_line(source, node.lineno),\n                            confidence=0.8,\n                        ))\n        return issues\n    \n    def _check_unused_variables(self, tree, path, source) -> List[ReviewIssue]:\n        \"\"\"检查未使用的变量\"\"\"\n        issues = []\n        \n        for node in ast.walk(tree):\n            if isinstance(node, ast.FunctionDef):\n                # 收集所有赋值\n                assigned = set()\n                used = set()\n                \n                for child in ast.walk(node):\n                    if isinstance(child, ast.Assign):\n                        for t in child.targets:\n                            if isinstance(t, ast.Name):\n                                assigned.add(t.id)\n                    elif isinstance(child, ast.Name) and isinstance(child.ctx, ast.Load):\n                        used.add(child.id)\n                \n                unused = assigned - used\n                for var in unused:\n                    if not var.startswith('_'):  # _ 前缀的变量约定为未使用\n                        issues.append(ReviewIssue(\n                            id=f\"ast-unused-var-{node.lineno}\",\n                            title=f\"未使用的变量: {var}\",\n                            description=f\"变量 `{var}` 在函数 `{node.name}` 中赋值后未使用。\",\n                            severity=Severity.INFO,\n                            category=IssueCategory.STYLE,\n                            location=CodeLocation(path, node.lineno, node.lineno),\n                            confidence=0.6,\n                        ))\n        \n        return issues",
      "section_ref": "27.3.2",
      "runnable": true,
      "dependencies": [
        "ast",
        "app"
      ]
    },
    {
      "id": "code-4",
      "language": "python",
      "description": "",
      "code": "# app/analyzers/security_analyzer.py\n\"\"\"安全漏洞扫描分析器\"\"\"\n\nimport re\nimport ast\nfrom typing import List\nfrom app.models.issue import ReviewIssue, Severity, IssueCategory, CodeLocation\n\n\nclass SecurityAnalyzer:\n    \"\"\"安全漏洞扫描器\"\"\"\n    \n    # 常见危险模式\n    DANGEROUS_PATTERNS = [\n        {\n            \"pattern\": r'(?:password|passwd|pwd|secret|api_key|apikey|token)\\s*=\\s*[\"\\'][^\"\\']+[\"\\']',\n            \"title\": \"硬编码敏感信息\",\n            \"description\": \"检测到硬编码的密码、密钥或令牌。应使用环境变量或密钥管理服务。\",\n            \"severity\": Severity.CRITICAL,\n            \"category\": IssueCategory.SECURITY,\n            \"cwe\": \"CWE-798\",\n            \"fix\": \"使用 os.environ.get('SECRET_NAME') 或配置管理工具。\",\n        },\n        {\n            \"pattern\": r'eval\\s*\\(',\n            \"title\": \"使用 eval()\",\n            \"description\": \"eval() 会执行任意 Python 代码，存在代码注入风险。\",\n            \"severity\": Severity.CRITICAL,\n            \"category\": IssueCategory.SECURITY,\n            \"cwe\": \"CWE-95\",\n            \"fix\": \"使用 ast.literal_eval() 或 json.loads() 替代。\",\n        },\n        {\n            \"pattern\": r'exec\\s*\\(',\n            \"title\": \"使用 exec()\",\n            \"description\": \"exec() 会执行任意代码，存在代码注入风险。\",\n            \"severity\": Severity.CRITICAL,\n            \"category\": IssueCategory.SECURITY,\n            \"cwe\": \"CWE-78\",\n            \"fix\": \"避免使用 exec()，考虑使用函数或策略模式。\",\n        },\n        {\n            \"pattern\": r'pickle\\.loads?\\s*\\(',\n            \"title\": \"不安全的反序列化\",\n            \"description\": \"pickle 反序列化可能执行任意代码。\",\n            \"severity\": Severity.CRITICAL,\n            \"category\": IssueCategory.SECURITY,\n            \"cwe\": \"CWE-502\",\n            \"fix\": \"使用 json 或其他安全序列化格式。\",\n        },\n        {\n            \"pattern\": r'subprocess\\.(?:call|run|Popen)\\s*\\([^)]*shell\\s*=\\s*True',\n            \"title\": \"Shell 注入风险\",\n            \"description\": \"subprocess 的 shell=True 参数可能导致命令注入。\",\n            \"severity\": Severity.ERROR,\n            \"category\": IssueCategory.SECURITY,\n            \"cwe\": \"CWE-78\",\n            \"fix\": \"使用 shell=False 并传递参数列表。\",\n        },\n        {\n            \"pattern\": r'(?:cursor\\.execute|\\.execute)\\s*\\(\\s*f[\"\\']|\\.format\\s*\\(|%\\s*[' + \"'\" + r']\\s*\\)',\n            \"title\": \"潜在的 SQL 注入\",\n            \"description\": \"检测到字符串拼接/格式化构建 SQL 语句。应使用参数化查询。\",\n            \"severity\": Severity.CRITICAL,\n            \"category\": IssueCategory.SECURITY,\n            \"cwe\": \"CWE-89\",\n            \"fix\": \"使用参数化查询：cursor.execute('SELECT * FROM users WHERE id = %s', (user_id,))\",\n        },\n        {\n            \"pattern\": r'(?:(?:verify|ssl_verify|cert_verify)\\s*=\\s*False)',\n            \"title\": \"禁用 SSL 证书验证\",\n            \"description\": \"禁用 SSL 验证会暴露中间人攻击风险。\",\n            \"severity\": Severity.ERROR,\n            \"category\": IssueCategory.SECURITY,\n            \"cwe\": \"CWE-295\",\n            \"fix\": \"启用 SSL 验证，或使用自定义 CA 证书。\",\n        },\n        {\n            \"pattern\": r'(?:requests|httpx|aiohttp)\\.(?:get|post|put|delete|patch)\\s*\\([^)]*verify\\s*=\\s*False',\n            \"title\": \"HTTP 请求禁用 SSL 验证\",\n            \"description\": \"HTTP 客户端禁用了 SSL 证书验证。\",\n            \"severity\": Severity.ERROR,\n            \"category\": IssueCategory.SECURITY,\n            \"cwe\": \"CWE-295\",\n            \"fix\": \"移除 verify=False 参数。\",\n        },\n        {\n            \"pattern\": r'debug\\s*=\\s*True',\n            \"title\": \"生产环境调试模式\",\n            \"description\": \"DEBUG=True 可能暴露敏感信息。\",\n            \"severity\": Severity.WARNING,\n            \"category\": IssueCategory.SECURITY,\n            \"cwe\": \"CWE-489\",\n            \"fix\": \"使用环境变量控制 DEBUG 状态。\",\n        },\n        {\n            \"pattern\": r'\\.raw\\s*=\\s*request\\.(?:body|data|get|post)',\n            \"title\": \"未验证的用户输入\",\n            \"description\": \"直接使用未经验证的请求数据。\",\n            \"severity\": Severity.WARNING,\n            \"category\": IssueCategory.SECURITY,\n            \"cwe\": \"CWE-20\",\n            \"fix\": \"使用 Pydantic 或其他验证库验证输入。\",\n        },\n    ]\n    \n    def analyze(self, file_path: str, source_code: str) -> List[ReviewIssue]:\n        \"\"\"安全扫描\"\"\"\n        issues = []\n        lines = source_code.split('\\n')\n        \n        for rule in self.DANGEROUS_PATTERNS:\n            pattern = re.compile(rule[\"pattern\"])\n            for i, line in enumerate(lines, 1):\n                if pattern.search(line):\n                    issues.append(ReviewIssue(\n                        id=f\"sec-{rule['cwe']}-{i}\",\n                        title=rule[\"title\"],\n                        description=rule[\"description\"],\n                        severity=rule[\"severity\"],\n                        category=rule[\"category\"],\n                        location=CodeLocation(file_path, i, i),\n                        code_snippet=line.strip(),\n                        fix_suggestion=rule[\"fix\"],\n                        confidence=0.85,\n                        cwe_id=rule[\"cwe\"],\n                    ))\n        \n        return issues",
      "section_ref": "27.3.3",
      "runnable": true,
      "dependencies": [
        "ast",
        "app"
      ]
    },
    {
      "id": "code-5",
      "language": "python",
      "description": "",
      "code": "# app/analyzers/performance_analyzer.py\n\"\"\"性能问题检测分析器\"\"\"\n\nimport ast\nimport re\nfrom typing import List\nfrom app.models.issue import ReviewIssue, Severity, IssueCategory, CodeLocation\n\n\nclass PerformanceAnalyzer:\n    \"\"\"性能问题检测器\"\"\"\n    \n    def analyze(self, file_path: str, source_code: str) -> List[ReviewIssue]:\n        issues = []\n        lines = source_code.split('\\n')\n        \n        issues.extend(self._check_string_concat_in_loop(\n            file_path, source_code, lines))\n        issues.extend(self._check_list_append_in_loop(\n            file_path, source_code, lines))\n        issues.extend(self._check_n_plus_one_pattern(\n            file_path, source_code))\n        issues.extend(self._check_global_lookup(\n            file_path, source_code, lines))\n        \n        return issues\n    \n    def _check_string_concat_in_loop(\n        self, path, source, lines\n    ) -> List[ReviewIssue]:\n        \"\"\"检测循环中的字符串拼接\"\"\"\n        issues = []\n        in_loop = False\n        loop_start = 0\n        \n        for i, line in enumerate(lines, 1):\n            stripped = line.strip()\n            if re.match(r'(?:for|while)\\s+', stripped):\n                in_loop = True\n                loop_start = i\n            elif in_loop and stripped and not stripped.startswith('#'):\n                if re.search(r'\\w+\\s*\\+=\\s*[\"\\']', stripped):\n                    issues.append(ReviewIssue(\n                        id=f\"perf-str-concat-{i}\",\n                        title=\"循环中字符串拼接\",\n                        description=(\n                            \"循环中使用 += 拼接字符串效率低下。\"\n                            \"每次拼接都会创建新字符串对象。\"),\n                        severity=Severity.INFO,\n                        category=IssueCategory.PERFORMANCE,\n                        location=CodeLocation(path, loop_start, i),\n                        code_snippet=stripped,\n                        fix_suggestion=\"使用列表收集后 join，或 io.StringIO\",\n                        fixed_code=(\n                            \"parts = []\\n\"\n                            \"for item in items:\\n\"\n                            \"    parts.append(str(item))\\n\"\n                            \"result = ''.join(parts)\"\n                        ),\n                        confidence=0.7,\n                    ))\n            elif in_loop and stripped == \"\":\n                in_loop = False\n        \n        return issues\n    \n    def _check_list_append_in_loop(\n        self, path, source, lines\n    ) -> List[ReviewIssue]:\n        \"\"\"检测可以用列表推导式替代的循环\"\"\"\n        issues = []\n        \n        try:\n            tree = ast.parse(source)\n        except SyntaxError:\n            return issues\n        \n        for node in ast.walk(tree):\n            if isinstance(node, ast.For):\n                # 检查 for 循环体是否只有简单的 append 操作\n                if (len(node.body) == 1 and\n                    isinstance(node.body[0], ast.Expr) and\n                    isinstance(node.body[0].value, ast.Call)):\n                    call = node.body[0].value\n                    if (isinstance(call.func, ast.Attribute) and\n                        call.func.attr == 'append'):\n                        issues.append(ReviewIssue(\n                            id=f\"perf-list-comp-{node.lineno}\",\n                            title=\"可用列表推导式替代\",\n                            description=(\n                                \"简单的 for 循环 + append 可以用列表推导式替代，\"\n                                \"更简洁且通常更快。\"),\n                            severity=Severity.INFO,\n                            category=IssueCategory.PERFORMANCE,\n                            location=CodeLocation(path, node.lineno,\n                                                 node.end_lineno or node.lineno),\n                            code_snippet=lines[node.lineno - 1].strip(),\n                            fix_suggestion=\"使用列表推导式\",\n                            fixed_code=(\n                                f\"result = [{ast.unparse(call.args[0])} \"\n                                f\"for {ast.unparse(node.target)} in \"\n                                f\"{ast.unparse(node.iter)}]\"\n                            ),\n                            confidence=0.75,\n                        ))\n        \n        return issues\n    \n    def _check_n_plus_one_pattern(\n        self, path, source\n    ) -> List[ReviewIssue]:\n        \"\"\"检测 N+1 查询模式\"\"\"\n        issues = []\n        \n        # 简单启发式：循环内有 execute/query 操作\n        pattern = re.compile(\n            r'for\\s+\\w+\\s+in\\s+\\w+.*:.*'\n            r'(?:execute|query|cursor|session\\.query|db\\.query)',\n            re.DOTALL\n        )\n        \n        if pattern.search(source):\n            issues.append(ReviewIssue(\n                id=f\"perf-n-plus-one-1\",\n                title=\"潜在的 N+1 查询问题\",\n                description=(\n                    \"检测到循环内可能有数据库查询。N+1 查询会导致大量数据库往返。\"\n                    \"建议使用批量查询（IN 子句、JOIN、prefetch_related 等）。\"),\n                severity=Severity.WARNING,\n                category=IssueCategory.PERFORMANCE,\n                location=CodeLocation(path, 1, 1),\n                fix_suggestion=\"使用批量查询替代循环查询\",\n                fixed_code=(\n                    \"# 不好\\n\"\n                    \"for user in users:\\n\"\n                    \"    orders = db.query(Order).filter_by(user_id=user.id)\\n\\n\"\n                    \"# 好\\n\"\n                    \"user_ids = [u.id for u in users]\\n\"\n                    \"orders = db.query(Order).filter(Order.user_id.in_(user_ids))\"\n                ),\n                confidence=0.5,  # 启发式，置信度较低\n            ))\n        \n        return issues\n    \n    def _check_global_lookup(self, path, source, lines):\n        \"\"\"检测循环中的全局变量查找\"\"\"\n        issues = []\n        try:\n            tree = ast.parse(source)\n        except SyntaxError:\n            return issues\n        \n        for node in ast.walk(tree):\n            if isinstance(node, (ast.For, ast.While)):\n                # 检查循环体内是否有 len() 调用\n                for child in ast.walk(node):\n                    if (isinstance(child, ast.Call) and\n                        isinstance(child.func, ast.Name) and\n                        child.func.id == 'len'):\n                        issues.append(ReviewIssue(\n                            id=f\"perf-len-in-loop-{node.lineno}\",\n                            title=\"循环中重复调用 len()\",\n                            description=\"循环中重复计算 len()，可将结果缓存到局部变量。\",\n                            severity=Severity.INFO,\n                            category=IssueCategory.PERFORMANCE,\n                            location=CodeLocation(path, node.lineno, node.lineno),\n                            fix_suggestion=\"将 len() 结果缓存到变量\",\n                            fixed_code=(\n                                \"n = len(items)  # 在循环外计算\\n\"\n                                \"for i in range(n):\\n\"\n                                \"    ...\"\n                            ),\n                            confidence=0.6,\n                        ))\n                        break  # 每个 loop 只报一次\n        \n        return issues",
      "section_ref": "27.3.4",
      "runnable": true,
      "dependencies": [
        "ast",
        "app"
      ]
    },
    {
      "id": "code-6",
      "language": "python",
      "description": "",
      "code": "# app/agents/review_agent.py\n\"\"\"代码审查协调 Agent\"\"\"\n\nimport os\nimport ast\nfrom typing import List, Optional\nfrom dataclasses import dataclass, field\nfrom app.models.issue import ReviewIssue, Severity, IssueCategory\nfrom app.analyzers.ast_analyzer import ASTAnalyzer\nfrom app.analyzers.security_analyzer import SecurityAnalyzer\nfrom app.analyzers.performance_analyzer import PerformanceAnalyzer\nfrom app.utils.llm_client import llm_client\n\n\n@dataclass\nclass FileReview:\n    \"\"\"单文件审查结果\"\"\"\n    file_path: str\n    language: str\n    total_lines: int\n    issues: List[ReviewIssue] = field(default_factory=list)\n    \n    @property\n    def critical_count(self) -> int:\n        return sum(1 for i in self.issues if i.severity == Severity.CRITICAL)\n    \n    @property\n    def error_count(self) -> int:\n        return sum(1 for i in self.issues if i.severity == Severity.ERROR)\n\n\n@dataclass\nclass ReviewReport:\n    \"\"\"审查报告\"\"\"\n    files: List[FileReview] = field(default_factory=list)\n    summary: str = \"\"\n    overall_score: float = 0.0  # 0-100\n    \n    @property\n    def total_issues(self) -> int:\n        return sum(len(f.issues) for f in self.files)\n    \n    @property\n    def critical_issues(self) -> List[ReviewIssue]:\n        return [i for f in self.files for i in f.issues\n                if i.severity == Severity.CRITICAL]\n\n\nclass ReviewAgent:\n    \"\"\"代码审查协调 Agent\"\"\"\n    \n    LANGUAGE_MAP = {\n        '.py': 'python',\n        '.js': 'javascript',\n        '.ts': 'typescript',\n        '.go': 'go',\n        '.java': 'java',\n        '.rs': 'rust',\n    }\n    \n    def __init__(self):\n        self._ast_analyzer = ASTAnalyzer()\n        self._security_analyzer = SecurityAnalyzer()\n        self._perf_analyzer = PerformanceAnalyzer()\n    \n    def review_file(self, file_path: str) -> FileReview:\n        \"\"\"审查单个文件\"\"\"\n        ext = os.path.splitext(file_path)[1]\n        language = self.LANGUAGE_MAP.get(ext, 'unknown')\n        \n        with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:\n            source = f.read()\n        \n        lines = source.count('\\n') + 1\n        issues = []\n        \n        if language == 'python':\n            issues.extend(self._ast_analyzer.analyze(file_path, source))\n        issues.extend(self._security_analyzer.analyze(file_path, source))\n        issues.extend(self._perf_analyzer.analyze(file_path, source))\n        \n        # 按严重程度排序\n        severity_order = {\n            Severity.CRITICAL: 0, Severity.ERROR: 1,\n            Severity.WARNING: 2, Severity.INFO: 3,\n        }\n        issues.sort(key=lambda x: severity_order.get(x.severity, 99))\n        \n        return FileReview(\n            file_path=file_path,\n            language=language,\n            total_lines=lines,\n            issues=issues,\n        )\n    \n    async def review_with_llm(\n        self, file_path: str, source_code: str\n    ) -> List[ReviewIssue]:\n        \"\"\"使用 LLM 进行深度语义审查\"\"\"\n        \n        system_prompt = \"\"\"你是一个资深代码审查专家。审查以下代码，找出潜在问题。\n\n审查维度：\n1. 逻辑错误和潜在 Bug\n2. 安全漏洞\n3. 性能问题\n4. 代码可维护性和可读性\n5. 最佳实践违反\n\n对每个问题提供：\n- 问题描述\n- 严重程度（critical/error/warning/info）\n- 修复建议\n- 修复后的代码\n\n返回 JSON 数组：\n[{\"title\":\"...\",\"description\":\"...\",\"severity\":\"...\",\"line\":N,\"fix\":\"...\",\"fixed_code\":\"...\"}]\"\"\"\n        \n        # 截取代码（避免超出 token 限制）\n        max_chars = 8000\n        code = source_code[:max_chars]\n        if len(source_code) > max_chars:\n            code += \"\\n\\n# ... (代码被截断)\"\n        \n        try:\n            import json\n            result = await llm_client.chat_json(\n                messages=[{\"role\": \"user\",\n                    \"content\": f\"文件: {file_path}\\n\\n```{code}```\"}],\n                system_prompt=system_prompt,\n            )\n            \n            issues = []\n            for item in result if isinstance(result, list) else []:\n                severity_map = {\n                    \"critical\": Severity.CRITICAL,\n                    \"error\": Severity.ERROR,\n                    \"warning\": Severity.WARNING,\n                    \"info\": Severity.INFO,\n                }\n                issues.append(ReviewIssue(\n                    id=f\"llm-{file_path}-{item.get('line', 0)}\",\n                    title=item.get(\"title\", \"\"),\n                    description=item.get(\"description\", \"\"),\n                    severity=severity_map.get(\n                        item.get(\"severity\", \"info\"), Severity.INFO),\n                    category=IssueCategory.BEST_PRACTICE,\n                    location=type('obj', (), {\n                        'file_path': file_path,\n                        '__str__': lambda self: f\"{file_path}:{item.get('line', 0)}\"\n                    })(),\n                    fix_suggestion=item.get(\"fix\", \"\"),\n                    fixed_code=item.get(\"fixed_code\", \"\"),\n                    confidence=0.7,\n                ))\n            return issues\n        except Exception:\n            return []\n    \n    def generate_summary(self, report: ReviewReport) -> str:\n        \"\"\"生成审查摘要\"\"\"\n        total = report.total_issues\n        critical = len(report.critical_issues)\n        \n        if critical > 0:\n            score = max(20, 100 - critical * 20 - total * 2)\n        else:\n            score = max(50, 100 - total * 5)\n        report.overall_score = score\n        \n        lines = [\n            f\"## 代码审查报告\",\n            f\"\",\n            f\"**审查文件**: {len(report.files)} 个\",\n            f\"**总代码行数**: {sum(f.total_lines for f in report.files)}\",\n            f\"**发现问题**: {total} 个\",\n            f\"  - 🔴 严重: {critical}\",\n            f\"  - 🟠 错误: {sum(f.error_count for f in report.files)}\",\n            f\"  - 🟡 警告: {sum(sum(1 for i in f.issues if i.severity == Severity.WARNING) for f in report.files)}\",\n            f\"  - 🔵 建议: {sum(sum(1 for i in f.issues if i.severity == Severity.INFO) for f in report.files)}\",\n            f\"\",\n            f\"**评分**: {score}/100\",\n        ]\n        \n        if critical > 0:\n            lines.append(f\"\\n### ⚠️ 严重问题（必须修复）\")\n            for issue in report.critical_issues[:5]:\n                lines.append(f\"- **{issue.title}** ({issue.location})\")\n        \n        return '\\n'.join(lines)\n    \n    async def generate_github_comment(\n        self, file_review: FileReview\n    ) -> str:\n        \"\"\"生成 GitHub PR 评论\"\"\"\n        if not file_review.issues:\n            return f\"✅ `{file_review.file_path}` 审查通过，没有发现问题。\"\n        \n        parts = [f\"## 🔍 `{file_review.file_path}` 审查结果\\n\"]\n        parts.append(f\"发现 **{len(file_review.issues)}** 个问题：\\n\")\n        \n        severity_emoji = {\n            Severity.CRITICAL: \"🔴\",\n            Severity.ERROR: \"🟠\",\n            Severity.WARNING: \"🟡\",\n            Severity.INFO: \"🔵\",\n        }\n        \n        for issue in file_review.issues[:10]:  # 最多显示 10 个\n            emoji = severity_emoji.get(issue.severity, \"⚪\")\n            parts.append(f\"### {emoji} {issue.title}\")\n            parts.append(f\"> {issue.description}\")\n            if issue.fix_suggestion:\n                parts.append(f\"\\n**建议**: {issue.fix_suggestion}\")\n            if issue.fixed_code:\n                parts.append(f\"\\n```python\\n{issue.fixed_code}\\n```\")\n            parts.append(\"\")\n        \n        return '\\n'.join(parts)",
      "section_ref": "27.3.5",
      "runnable": true,
      "dependencies": [
        "ast",
        "app"
      ]
    },
    {
      "id": "code-7",
      "language": "python",
      "description": "",
      "code": "# app/git/git_client.py\n\"\"\"Git 集成客户端\"\"\"\n\nimport subprocess\nimport os\nfrom typing import List, Optional\nfrom dataclasses import dataclass\n\n\n@dataclass\nclass GitDiff:\n    file_path: str\n    status: str  # added, modified, deleted\n    additions: int\n    deletions: int\n    patch: str = \"\"\n\n\nclass GitClient:\n    \"\"\"Git 命令行封装\"\"\"\n    \n    def __init__(self, repo_path: str = \".\"):\n        self.repo_path = os.path.abspath(repo_path)\n    \n    def _run(self, *args) -> str:\n        result = subprocess.run(\n            [\"git\"] + list(args),\n            cwd=self.repo_path,\n            capture_output=True, text=True, timeout=30,\n        )\n        if result.returncode != 0:\n            raise RuntimeError(f\"git {' '.join(args)} failed: {result.stderr}\")\n        return result.stdout.strip()\n    \n    def get_changed_files(\n        self, base: str = \"main\", head: str = \"HEAD\"\n    ) -> List[GitDiff]:\n        \"\"\"获取变更文件列表\"\"\"\n        output = self._run(\n            \"diff\", \"--numstat\", f\"{base}...{head}\"\n        )\n        \n        diffs = []\n        for line in output.split('\\n'):\n            if not line.strip():\n                continue\n            parts = line.split('\\t')\n            if len(parts) >= 3:\n                additions = int(parts[0]) if parts[0] != '-' else 0\n                deletions = int(parts[1]) if parts[1] != '-' else 0\n                path = parts[2]\n                diffs.append(GitDiff(\n                    file_path=path,\n                    status=\"modified\",\n                    additions=additions,\n                    deletions=deletions,\n                ))\n        \n        return diffs\n    \n    def get_file_content(self, file_path: str, ref: str = \"HEAD\") -> str:\n        \"\"\"获取指定版本的文件内容\"\"\"\n        return self._run(\"show\", f\"{ref}:{file_path}\")\n    \n    def get_current_branch(self) -> str:\n        return self._run(\"branch\", \"--show-current\")",
      "section_ref": "27.3.6",
      "runnable": true,
      "dependencies": [
        "subprocess"
      ]
    },
    {
      "id": "code-8",
      "language": "python",
      "description": "",
      "code": "# app/main.py\n\"\"\"代码审查助手 - FastAPI 入口\"\"\"\n\nfrom fastapi import FastAPI, BackgroundTasks\nfrom pydantic import BaseModel\nfrom typing import Optional, List\nfrom contextlib import asynccontextmanager\n\nfrom app.agents.review_agent import ReviewAgent, ReviewReport\nfrom app.git.git_client import GitClient\nfrom app.config import settings\n\n\nreview_agent = ReviewAgent()\nreports_store: dict = {}\n\n\nclass ReviewRequest(BaseModel):\n    repo_path: str = \".\"\n    base_branch: str = \"main\"\n    target_branch: str = \"HEAD\"\n    files: Optional[List[str]] = None  # 指定文件，None 则审查全部变更\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    print(\"🔍 Code Review Assistant 启动完成\")\n    yield\n\n\napp = FastAPI(title=\"代码审查助手\",\n              version=\"1.0.0\", lifespan=lifespan)\n\n\n@app.post(\"/api/v1/review\")\nasync def review_code(req: ReviewRequest):\n    \"\"\"执行代码审查\"\"\"\n    git = GitClient(req.repo_path)\n    report = ReviewReport()\n    \n    if req.files:\n        changed = [type('obj', (), {\n            'file_path': f, 'status': 'modified',\n            'additions': 0, 'deletions': 0})() for f in req.files]\n    else:\n        changed = git.get_changed_files(req.base_branch, req.target_branch)\n    \n    for diff in changed:\n        try:\n            source = git.get_file_content(diff.file_path)\n            file_review = review_agent.review_file(\n                f\"{req.repo_path}/{diff.file_path}\"\n            )\n            report.files.append(file_review)\n        except Exception as e:\n            report.files.append(type('obj', (), {\n                'file_path': diff.file_path,\n                'language': 'unknown',\n                'total_lines': 0,\n                'issues': [],\n                'critical_count': 0,\n                'error_count': 0,\n                '__str__': lambda: diff.file_path,\n            })())\n    \n    summary = review_agent.generate_summary(report)\n    report.summary = summary\n    \n    import uuid\n    report_id = str(uuid.uuid4())[:8]\n    reports_store[report_id] = report\n    \n    return {\n        \"report_id\": report_id,\n        \"total_files\": len(report.files),\n        \"total_issues\": report.total_issues,\n        \"critical\": len(report.critical_issues),\n        \"score\": report.overall_score,\n        \"summary\": summary,\n    }\n\n\n@app.get(\"/api/v1/review/{report_id}\")\nasync def get_review_report(report_id: str):\n    \"\"\"获取审查报告详情\"\"\"\n    report = reports_store.get(report_id)\n    if not report:\n        return {\"error\": \"Report not found\"}\n    \n    return {\n        \"summary\": report.summary,\n        \"score\": report.overall_score,\n        \"files\": [\n            {\n                \"path\": f.file_path,\n                \"language\": f.language,\n                \"lines\": f.total_lines,\n                \"issues\": [\n                    {\n                        \"title\": i.title,\n                        \"severity\": i.severity.value,\n                        \"category\": i.category.value,\n                        \"location\": str(i.location),\n                        \"description\": i.description,\n                        \"fix\": i.fix_suggestion,\n                        \"confidence\": i.confidence,\n                    }\n                    for i in f.issues\n                ],\n            }\n            for f in report.files\n        ],\n    }\n\n\n@app.get(\"/api/v1/review/{report_id}/comment\")\nasync def get_github_comment(report_id: str):\n    \"\"\"获取 GitHub PR 评论格式的审查结果\"\"\"\n    report = reports_store.get(report_id)\n    if not report:\n        return {\"error\": \"Report not found\"}\n    \n    comments = []\n    for f in report.files:\n        comment = await review_agent.generate_github_comment(f)\n        comments.append(comment)\n    \n    return {\"comments\": comments}\n\n\nif __name__ == \"__main__\":\n    import uvicorn\n    uvicorn.run(\"app.main:app\", host=\"0.0.0.0\", port=8001)",
      "section_ref": "27.3.7",
      "runnable": true,
      "dependencies": [
        "fastapi",
        "pydantic",
        "contextlib",
        "app"
      ]
    },
    {
      "id": "code-9",
      "language": "python",
      "description": "---",
      "code": "# tests/test_analyzers.py\n\"\"\"分析器测试\"\"\"\n\nimport pytest\nfrom app.analyzers.ast_analyzer import ASTAnalyzer\nfrom app.analyzers.security_analyzer import SecurityAnalyzer\nfrom app.analyzers.performance_analyzer import PerformanceAnalyzer\n\n\nclass TestASTAnalyzer:\n    \n    def test_bare_except(self):\n        code = \"\"\"\ntry:\n    do_something()\nexcept:\n    pass\n\"\"\"\n        analyzer = ASTAnalyzer()\n        issues = analyzer.analyze(\"test.py\", code)\n        assert any(\"bare except\" in i.title.lower() for i in issues)\n    \n    def test_mutable_default(self):\n        code = \"\"\"\ndef add_item(item, items=[]):\n    items.append(item)\n    return items\n\"\"\"\n        analyzer = ASTAnalyzer()\n        issues = analyzer.analyze(\"test.py\", code)\n        assert any(\"可变\" in i.title for i in issues)\n    \n    def test_shadow_builtin(self):\n        code = \"\"\"\ndef process(list):\n    return [x for x in list]\n\"\"\"\n        analyzer = ASTAnalyzer()\n        issues = analyzer.analyze(\"test.py\", code)\n        assert any(\"内置\" in i.title for i in issues)\n\n\nclass TestSecurityAnalyzer:\n    \n    def test_hardcoded_password(self):\n        code = 'password = \"my_secret_123\"'\n        analyzer = SecurityAnalyzer()\n        issues = analyzer.analyze(\"test.py\", code)\n        assert any(\"硬编码\" in i.title for i in issues)\n    \n    def test_eval_usage(self):\n        code = \"result = eval(user_input)\"\n        analyzer = SecurityAnalyzer()\n        issues = analyzer.analyze(\"test.py\", code)\n        assert any(\"eval\" in i.title for i in issues)\n    \n    def test_sql_injection(self):\n        code = 'query = f\"SELECT * FROM users WHERE id = {user_id}\"'\n        analyzer = SecurityAnalyzer()\n        issues = analyzer.analyze(\"test.py\", code)\n        assert any(\"SQL\" in i.title for i in issues)\n    \n    def test_pickle_deserialize(self):\n        code = \"data = pickle.loads(received_data)\"\n        analyzer = SecurityAnalyzer()\n        issues = analyzer.analyze(\"test.py\", code)\n        assert any(\"反序列化\" in i.title for i in issues)",
      "section_ref": "27.4",
      "runnable": true,
      "dependencies": [
        "pytest",
        "app"
      ]
    }
  ],
  "tables": [
    {
      "headers": [
        "功能",
        "描述",
        "优先级"
      ],
      "data": [
        [
          "AST 语法分析",
          "解析代码结构，检测语法问题",
          "P0"
        ],
        [
          "代码规范检查",
          "PEP8、命名规范、文档规范",
          "P0"
        ],
        [
          "安全漏洞扫描",
          "SQL注入、XSS、硬编码密钥等",
          "P0"
        ],
        [
          "性能问题检测",
          "N+1 查询、内存泄漏、不必要的复制",
          "P1"
        ],
        [
          "复杂度分析",
          "圈复杂度、认知复杂度",
          "P1"
        ],
        [
          "修复建议生成",
          "问题代码 + 修复代码对比",
          "P0"
        ],
        [
          "Git PR 集成",
          "自动评论、状态检查",
          "P1"
        ],
        [
          "审查报告生成",
          "HTML/PDF 格式的完整报告",
          "P2"
        ]
      ]
    }
  ],
  "key_takeaways": [],
  "common_pitfalls": [],
  "related_chapters": [
    "ch16"
  ]
}