Skip to content

Latest commit

 

History

History
2400 lines (1873 loc) · 68 KB

File metadata and controls

2400 lines (1873 loc) · 68 KB

代码检查功能二次开发指南

面向开发者的代码检查系统架构、扩展和定制指南

📋 目录


架构概览

系统架构图

┌─────────────────────────────────────────────────────────┐
│                    Chat Auto Coder                      │
│                     (主应用)                             │
└────────────────────┬────────────────────────────────────┘
                     │
                     │ 加载插件
                     ▼
┌─────────────────────────────────────────────────────────┐
│              CodeCheckerPlugin                          │
│            (命令注册和调度)                              │
│  - 注册 /check 命令                                      │
│  - 解析参数                                              │
│  - 调用检查器                                            │
└────────────────────┬────────────────────────────────────┘
                     │
        ┌────────────┼────────────┐
        ▼            ▼            ▼
┌──────────┐  ┌──────────┐  ┌──────────┐
│  /check  │  │  /check  │  │  /check  │
│  /file   │  │ /folder  │  │ /resume  │
└─────┬────┘  └─────┬────┘  └─────┬────┘
      │             │              │
      └─────────────┼──────────────┘
                    ▼
        ┌───────────────────────┐
        │    CodeChecker        │
        │   (核心检查逻辑)       │
        │  - 协调各模块          │
        │  - 调用 LLM           │
        │  - 结果处理            │
        └───────────┬───────────┘
                    │
        ┌───────────┼───────────┐
        ▼           ▼           ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│  Rules   │ │  File    │ │Progress  │
│  Loader  │ │Processor │ │ Tracker  │
└──────────┘ └──────────┘ └──────────┘
        │           │           │
        └───────────┼───────────┘
                    ▼
        ┌───────────────────────┐
        │   Report Generator    │
        │   (生成检查报告)       │
        └───────────────────────┘

模块关系

模块 文件 职责 依赖
插件 code_checker_plugin.py 命令注册、参数解析 CodeChecker
核心检查器 core.py 检查逻辑、LLM 调用 所有模块
规则加载器 rules_loader.py 规则文件解析 types.py
文件处理器 file_processor.py 文件扫描、分块 types.py
进度跟踪器 progress_tracker.py 进度持久化 types.py
报告生成器 report_generator.py 报告生成 types.py
类型定义 types.py 数据模型 pydantic

数据流

1. 用户输入命令
   ↓
2. Plugin 解析参数
   ↓
3. CodeChecker.check_file() 或 check_files()
   ↓
4. RulesLoader 加载规则
   ↓
5. FileProcessor 扫描/分块文件
   ↓
6. CodeChecker 调用 LLM 检查
   ↓
7. 解析 LLM 返回的 JSON
   ↓
8. ReportGenerator 生成报告
   ↓
9. ProgressTracker 保存进度
   ↓
10. 返回结果给用户

核心模块详解

1. types.py - 类型定义

位置autocoder/checker/types.py

核心类型

Severity - 严重程度枚举

class Severity(str, Enum):
    ERROR = "error"      # 错误:必须修复
    WARNING = "warning"  # 警告:建议修复
    INFO = "info"        # 提示:可选修复

Rule - 规则定义

class Rule(BaseModel):
    id: str                    # 规则ID,如 "backend_001"
    category: str              # 规则类别,如 "代码结构"
    title: str                 # 规则标题
    description: str           # 规则描述
    severity: Severity         # 严重程度
    enabled: bool = True       # 是否启用
    examples: Optional[str]    # 示例代码

Issue - 检查问题

class Issue(BaseModel):
    rule_id: str              # 违反的规则ID
    severity: Severity        # 严重程度
    line_start: int           # 问题起始行号
    line_end: int             # 问题结束行号
    description: str          # 问题描述
    suggestion: str           # 修复建议
    code_snippet: str         # 问题代码片段

FileCheckResult - 文件检查结果

class FileCheckResult(BaseModel):
    file_path: str                # 文件路径
    check_time: str              # 检查时间
    issues: List[Issue]          # 问题列表
    error_count: int             # 错误数量
    warning_count: int           # 警告数量
    info_count: int              # 提示数量
    status: str                  # "success" | "failed" | "skipped"
    error_message: Optional[str] # 错误信息(如果失败)

CheckState - 检查状态(用于持久化)

class CheckState(BaseModel):
    check_id: str                  # 检查ID
    start_time: str               # 开始时间
    config: Dict[str, Any]        # 配置参数
    total_files: List[str]        # 总文件列表
    completed_files: List[str]    # 已完成文件
    remaining_files: List[str]    # 剩余文件

扩展建议

  • 添加新的严重程度级别(如 CRITICAL)
  • 添加规则分组(RuleGroup)
  • 添加检查统计信息(CheckStatistics)

2. rules_loader.py - 规则加载器

位置autocoder/checker/rules_loader.py

核心功能

  • 加载 Markdown 格式的规则文件
  • 解析规则配置
  • 根据文件类型选择适用规则
  • 规则缓存优化

关键方法

load_rules() - 加载规则

def load_rules(self, rule_type: str) -> List[Rule]:
    """
    加载指定类型的规则

    Args:
        rule_type: 规则类型(backend/frontend)

    Returns:
        规则列表
    """
    # 1. 检查缓存
    if rule_type in self._rule_cache:
        return self._rule_cache[rule_type]

    # 2. 加载规则文件
    rule_file = os.path.join(self.rules_dir, f"{rule_type}_rules.md")
    rules = self._parse_markdown_rules(rule_file)

    # 3. 应用配置
    rules = self._apply_config(rules, rule_type)

    # 4. 缓存结果
    self._rule_cache[rule_type] = rules

    return rules

_parse_markdown_rules() - 解析 Markdown 规则

def _parse_markdown_rules(self, file_path: str) -> List[Rule]:
    """
    解析 Markdown 格式的规则文件

    规则格式:
    ### 规则ID: backend_001
    **标题**: 规则标题
    **严重程度**: warning
    **描述**: 规则描述
    """
    rules = []

    with open(file_path, 'r', encoding='utf-8') as f:
        content = f.read()

    # 按 "### 规则ID:" 分割
    sections = re.split(r'### 规则ID:\s*(\w+)', content)

    current_category = ""

    for i in range(1, len(sections), 2):
        rule_id = sections[i].strip()
        rule_content = sections[i+1]

        # 提取字段
        title = self._extract_field(rule_content, "标题")
        severity = self._extract_field(rule_content, "严重程度")
        description = self._extract_field(rule_content, "描述")

        # 提取示例
        examples = self._extract_examples(rule_content)

        rules.append(Rule(
            id=rule_id,
            category=current_category,
            title=title,
            description=description,
            severity=Severity(severity),
            examples=examples
        ))

    return rules

扩展建议

  • 支持 YAML/JSON 格式的规则文件
  • 支持规则继承和覆盖
  • 支持规则优先级
  • 支持动态规则(从数据库加载)

3. file_processor.py - 文件处理器

位置autocoder/checker/file_processor.py

核心功能

  • 文件扫描和过滤
  • 大文件分块
  • 文件类型检测

关键方法

scan_files() - 扫描文件

def scan_files(self, path: str, filters: FileFilters) -> List[str]:
    """
    扫描目录,返回符合条件的文件列表

    Args:
        path: 扫描路径
        filters: 文件过滤器(扩展名、忽略模式)

    Returns:
        文件路径列表
    """
    files = []

    for root, dirs, filenames in os.walk(path):
        # 过滤忽略的目录
        dirs[:] = [d for d in dirs if not self._should_ignore(d, filters.ignored)]

        for filename in filenames:
            file_path = os.path.join(root, filename)

            # 检查扩展名
            if filters.extensions:
                if not any(file_path.endswith(ext) for ext in filters.extensions):
                    continue

            # 检查忽略模式
            if self._should_ignore(file_path, filters.ignored):
                continue

            # 检查是否可检查
            if self.is_checkable(file_path):
                files.append(file_path)

    return files

chunk_file() - 文件分块

def chunk_file(self, file_path: str) -> List[CodeChunk]:
    """
    将大文件分块,确保每块不超过 token 限制

    策略:
    1. 为每行添加行号
    2. 计算总 token 数
    3. 如果超过限制,按 token 分块
    4. 块之间有重叠,避免边界问题
    """
    with open(file_path, 'r', encoding='utf-8') as f:
        lines = f.readlines()

    # 添加行号
    numbered_lines = [f"{i+1} {line}" for i, line in enumerate(lines)]

    # 计算 tokens
    total_tokens = count_tokens('\n'.join(numbered_lines))

    # 不需要分块
    if total_tokens <= self.chunk_size:
        return [CodeChunk(
            content='\n'.join(numbered_lines),
            start_line=1,
            end_line=len(lines),
            chunk_index=0,
            total_chunks=1
        )]

    # 需要分块
    chunks = []
    current_line = 0
    chunk_index = 0

    while current_line < len(numbered_lines):
        # 计算当前 chunk 的结束行
        end_line = self._calculate_chunk_end(
            numbered_lines,
            current_line,
            self.chunk_size
        )

        # 创建 chunk
        chunk_content = '\n'.join(numbered_lines[current_line:end_line])
        chunks.append(CodeChunk(
            content=chunk_content,
            start_line=current_line + 1,
            end_line=end_line,
            chunk_index=chunk_index,
            total_chunks=0  # 稍后更新
        ))

        # 移动到下一个 chunk(考虑重叠)
        current_line = end_line - self.overlap
        chunk_index += 1

    # 更新总 chunk 数
    for chunk in chunks:
        chunk.total_chunks = len(chunks)

    return chunks

扩展建议

  • 支持智能分块(按函数/类边界)
  • 支持增量检查(只检查修改的文件)
  • 支持 Git diff 集成
  • 支持文件内容缓存

4. core.py - 核心检查器

位置autocoder/checker/core.py

核心功能

  • 协调各模块完成检查
  • 调用 LLM 进行代码检查
  • 处理检查结果
  • 管理并发执行

关键方法

check_file() - 单文件检查

def check_file(self, file_path: str) -> FileCheckResult:
    """
    检查单个文件

    流程:
    1. 加载适用规则
    2. 读取文件并分块
    3. 检查每个 chunk
    4. 合并结果
    5. 返回检查结果
    """
    try:
        start_time = datetime.now()

        # 1. 获取适用规则
        rules = self.rules_loader.get_applicable_rules(file_path)
        if not rules:
            return FileCheckResult(
                file_path=file_path,
                check_time=start_time.isoformat(),
                issues=[],
                status="skipped"
            )

        # 2. 分块处理
        chunks = self.file_processor.chunk_file(file_path)
        logger.info(f"文件 {file_path} 被分为 {len(chunks)} 个 chunks")

        # 3. 检查每个 chunk
        all_issues = []
        for chunk in chunks:
            issues = self.check_code_chunk(chunk.content, rules)
            all_issues.extend(issues)

        # 4. 合并重复问题
        merged_issues = self._merge_duplicate_issues(all_issues)

        # 5. 统计
        error_count = sum(1 for i in merged_issues if i.severity == Severity.ERROR)
        warning_count = sum(1 for i in merged_issues if i.severity == Severity.WARNING)
        info_count = sum(1 for i in merged_issues if i.severity == Severity.INFO)

        return FileCheckResult(
            file_path=file_path,
            check_time=datetime.now().isoformat(),
            issues=merged_issues,
            error_count=error_count,
            warning_count=warning_count,
            info_count=info_count,
            status="success"
        )

    except Exception as e:
        logger.error(f"检查文件 {file_path} 失败: {e}")
        return FileCheckResult(
            file_path=file_path,
            check_time=datetime.now().isoformat(),
            issues=[],
            error_count=0,
            warning_count=0,
            info_count=0,
            status="failed",
            error_message=str(e)
        )

check_code_chunk() - 检查代码块

@byzerllm.prompt()
def check_code_prompt(self, code_with_lines: str, rules: str) -> str:
    """
    你是一个代码审查专家。请根据提供的规则检查代码,找出不符合规范的地方。

    ## 检查规则

    {{ rules }}

    ## 待检查代码(带行号)

    ```
    {{ code_with_lines }}
    ```

    ## 输出要求

    请仔细检查代码,对于每个发现的问题:
    1. 准确定位问题的起始和结束行号
    2. 引用违反的规则ID
    3. 描述问题
    4. 提供修复建议

    以 JSON 数组格式输出,每个问题包含:
    - rule_id: 违反的规则ID
    - severity: 严重程度 (error/warning/info)
    - line_start: 问题起始行号(整数)
    - line_end: 问题结束行号(整数)
    - description: 问题描述
    - suggestion: 修复建议

    如果没有发现问题,返回空数组 []

    示例输出:
    ```json
    [
        {
            "rule_id": "backend_006",
            "severity": "warning",
            "line_start": 15,
            "line_end": 32,
            "description": "发现复杂的 if-else 嵌套",
            "suggestion": "建议将内层逻辑抽取为独立方法"
        }
    ]
    ```
    """

def check_code_chunk(self, code: str, rules: List[Rule]) -> List[Issue]:
    """调用 LLM 检查代码块"""
    # 格式化规则
    rules_text = self._format_rules(rules)

    # 调用 LLM
    response = self.llm.chat_oai(
        conversations=[{
            "role": "user",
            "content": self.check_code_prompt(code, rules_text)
        }]
    )[0].output

    # 解析 JSON
    issues = self._parse_llm_response(response)

    return issues

check_files_concurrent() - 并发检查

def check_files_concurrent(
    self, files: List[str], max_workers: int = 5
) -> Generator[FileCheckResult, None, None]:
    """
    并发检查多个文件

    使用 ThreadPoolExecutor 实现并发检查,提高大型项目的检查速度。
    使用生成器模式按完成顺序实时返回结果,适合与进度条配合使用。

    Args:
        files: 文件列表
        max_workers: 最大并发数

    Yields:
        FileCheckResult: 按完成顺序返回检查结果
    """
    logger.info(f"开始并发检查 {len(files)} 个文件 (workers={max_workers})")

    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        # 提交所有任务
        future_to_file = {
            executor.submit(self.check_file, file_path): file_path
            for file_path in files
        }

        # 按完成顺序返回结果
        for future in as_completed(future_to_file):
            file_path = future_to_file[future]
            try:
                result = future.result()
                yield result
            except Exception as exc:
                logger.error(f"文件 {file_path} 检查失败: {exc}")
                yield FileCheckResult(
                    file_path=file_path,
                    check_time=datetime.now().isoformat(),
                    issues=[],
                    error_count=0,
                    warning_count=0,
                    info_count=0,
                    status="failed",
                    error_message=str(exc)
                )

扩展建议

  • 支持自定义 Prompt 模板
  • 支持多模型对比检查
  • 支持增量检查优化
  • 支持检查结果缓存

5. progress_tracker.py - 进度跟踪器

位置autocoder/checker/progress_tracker.py

核心功能

  • 跟踪检查进度
  • 持久化检查状态
  • 支持中断恢复

关键方法

start_check() - 开始检查

def start_check(self, files: List[str], config: Dict[str, Any]) -> str:
    """
    开始新的检查,返回 check_id

    Args:
        files: 文件列表
        config: 检查配置

    Returns:
        check_id: 格式为 {project}_{timestamp}
    """
    # 生成 check_id
    project_name = os.path.basename(os.getcwd())
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    check_id = f"{project_name}_{timestamp}"

    # 创建检查状态
    state = CheckState(
        check_id=check_id,
        start_time=datetime.now().isoformat(),
        config=config,
        total_files=files,
        completed_files=[],
        remaining_files=files
    )

    # 保存状态
    self.save_state(check_id, state)

    return check_id

mark_completed() - 标记完成

def mark_completed(self, check_id: str, file_path: str) -> None:
    """标记文件已完成检查"""
    # 加载状态
    state = self.load_state(check_id)

    # 更新状态
    if file_path in state.remaining_files:
        state.remaining_files.remove(file_path)

    if file_path not in state.completed_files:
        state.completed_files.append(file_path)

    # 保存状态
    self.save_state(check_id, state)

resume_check() - 恢复检查

def get_remaining_files(self, check_id: str) -> List[str]:
    """获取待检查的文件列表"""
    state = self.load_state(check_id)
    return state.remaining_files if state else []

扩展建议

  • 支持检查历史管理
  • 支持检查统计分析
  • 支持多用户检查隔离
  • 支持分布式检查协调

6. report_generator.py - 报告生成器

位置autocoder/checker/report_generator.py

核心功能

  • 生成 JSON 格式报告
  • 生成 Markdown 格式报告
  • 生成汇总统计

关键方法

generate_file_report() - 生成文件报告

def generate_file_report(self, result: FileCheckResult, report_dir: str) -> None:
    """生成单个文件的检查报告"""
    # 创建报告目录
    files_dir = os.path.join(report_dir, "files")
    os.makedirs(files_dir, exist_ok=True)

    # 生成文件名(替换路径分隔符)
    safe_filename = result.file_path.replace("/", "_").replace("\\", "_")

    # 生成 JSON 报告
    json_path = os.path.join(files_dir, f"{safe_filename}.json")
    with open(json_path, 'w', encoding='utf-8') as f:
        json.dump(result.dict(), f, indent=2, ensure_ascii=False)

    # 生成 Markdown 报告
    md_path = os.path.join(files_dir, f"{safe_filename}.md")
    md_content = self._generate_file_markdown(result)
    with open(md_path, 'w', encoding='utf-8') as f:
        f.write(md_content)

generate_summary_report() - 生成汇总报告

def generate_summary_report(
    self, results: List[FileCheckResult], report_dir: str
) -> None:
    """生成汇总报告"""
    # 统计信息
    total_files = len(results)
    total_issues = sum(len(r.issues) for r in results)
    total_errors = sum(r.error_count for r in results)
    total_warnings = sum(r.warning_count for r in results)
    total_infos = sum(r.info_count for r in results)

    # 问题最多的文件
    top_files = sorted(
        results,
        key=lambda r: len(r.issues),
        reverse=True
    )[:10]

    # 按规则分类
    rule_counts = {}
    for result in results:
        for issue in result.issues:
            rule_counts[issue.rule_id] = rule_counts.get(issue.rule_id, 0) + 1

    # 生成汇总数据
    summary = {
        "check_id": os.path.basename(report_dir),
        "total_files": total_files,
        "total_issues": total_issues,
        "error_count": total_errors,
        "warning_count": total_warnings,
        "info_count": total_infos,
        "top_files": [
            {"file": f.file_path, "issues": len(f.issues)}
            for f in top_files
        ],
        "rule_distribution": rule_counts
    }

    # 保存 JSON
    with open(os.path.join(report_dir, "summary.json"), 'w') as f:
        json.dump(summary, f, indent=2, ensure_ascii=False)

    # 保存 Markdown
    md_content = self._generate_summary_markdown(summary, results)
    with open(os.path.join(report_dir, "summary.md"), 'w') as f:
        f.write(md_content)

扩展建议

  • 支持 HTML 报告
  • 支持 PDF 导出
  • 支持趋势分析(多次检查对比)
  • 支持自定义报告模板

插件系统

CodeCheckerPlugin

位置autocoder/plugins/code_checker_plugin.py

核心功能

  • 注册 /check 命令及子命令
  • 提供命令补全
  • 参数解析和验证
  • 调用核心检查逻辑

插件结构

class CodeCheckerPlugin(Plugin):
    name = "code_checker"
    description = "代码规范检查插件"
    version = "1.0.0"

    def initialize(self) -> bool:
        """初始化插件"""
        # 初始化核心组件
        self.checker = CodeChecker(self.llm, self.args)
        self.report_generator = ReportGenerator()
        self.progress_tracker = ProgressTracker()

        return True

    def get_commands(self):
        """注册命令"""
        return {
            "check": (self.handle_check, "代码检查命令"),
        }

    def get_completions(self):
        """提供命令补全"""
        return {
            "/check": ["/file", "/folder", "/resume", "/report"],
            "/check /folder": ["/path", "/ext", "/ignore", "/workers"],
        }

命令处理

def handle_check(self, args: str):
    """处理 /check 命令"""
    parts = args.strip().split(maxsplit=1)

    if not parts:
        print(self._show_help())
        return

    subcommand = parts[0]

    if subcommand == "/file":
        self._check_file(parts[1] if len(parts) > 1 else "")
    elif subcommand == "/folder":
        self._check_folder(parts[1] if len(parts) > 1 else "")
    elif subcommand == "/resume":
        self._resume_check(parts[1] if len(parts) > 1 else "")
    else:
        print(f"未知子命令: {subcommand}")
        print(self._show_help())

扩展插件

  1. 创建新插件

    class MyCheckerPlugin(CodeCheckerPlugin):
        name = "my_checker"
    
        def initialize(self):
            super().initialize()
            # 自定义初始化
            return True
    
        def get_commands(self):
            commands = super().get_commands()
            # 添加新命令
            commands["mycheck"] = (self.handle_mycheck, "我的检查")
            return commands
  2. 注册插件: 在 autocoder/plugins/__init__.py 中注册


添加新规则

规则文件格式

规则文件使用 Markdown 格式,便于阅读和维护。

文件位置

  • 后端规则:rules/backend_rules.md
  • 前端规则:rules/frontend_rules.md

规则格式

## 规则类别名称

### 规则ID: backend_xxx
**标题**: 规则标题
**严重程度**: warning
**描述**: 规则的详细描述,说明为什么需要这个规则

**错误示例**:
```python
# 错误的代码示例
def bad_example():
    # 违反规则的代码
    pass

正确示例:

# 正确的代码示例
def good_example():
    # 符合规则的代码
    pass

### 添加新规则步骤

1. **编辑规则文件**

   ```bash
   vim rules/backend_rules.md
  1. 添加规则

    ## 性能优化
    
    ### 规则ID: backend_100
    **标题**: 避免在循环中进行数据库查询
    **严重程度**: error
    **描述**: 在循环中进行数据库查询会导致 N+1 问题,严重影响性能。应该使用批量查询或 JOIN 优化。
    
    **错误示例**:
    ```python
    def get_user_posts(user_ids):
        posts = []
        for user_id in user_ids:
            # 每次循环都查询数据库
            user_posts = db.query(Post).filter(Post.user_id == user_id).all()
            posts.extend(user_posts)
        return posts

    正确示例:

    def get_user_posts(user_ids):
        # 批量查询
        posts = db.query(Post).filter(Post.user_id.in_(user_ids)).all()
        return posts
    
    
  2. 更新规则配置(可选)

    如果需要特殊配置,编辑 rules/rules_config.json

    {
      "rule_sets": {
        "backend": {
          "enabled": true,
          "disabled_rules": []  // 如需禁用某规则,添加到这里
        }
      }
    }
  3. 测试规则

    # 启动 chat_auto_coder
    python -m autocoder.chat_auto_coder
    
    # 测试新规则
    /check /file test_file.py
  4. 验证结果

    检查生成的报告,确认新规则正确生效

规则最佳实践

  1. 规则ID规范

    • 后端:backend_001 - backend_999
    • 前端:frontend_001 - frontend_999
    • 按类别分段(如 001-099 代码结构,100-199 性能优化)
  2. 严重程度选择

    • error:必须修复的问题(安全、致命bug)
    • warning:建议修复的问题(性能、规范)
    • info:提示性信息(优化建议)
  3. 描述编写

    • 说明"为什么"而不只是"是什么"
    • 提供具体的修复建议
    • 包含正反示例
  4. 示例代码

    • 使用实际场景的代码
    • 示例简洁明了
    • 突出关键问题

扩展新功能

1. 添加新的检查类型

假设要添加 SQL 检查:

步骤 1:创建规则文件

# 创建 SQL 规则文件
vim rules/sql_rules.md

步骤 2:编写规则

## SQL 规范

### 规则ID: sql_001
**标题**: 避免使用 SELECT *
**严重程度**: warning
**描述**: SELECT * 会查询所有字段,影响性能,且在表结构变更时可能导致问题

**错误示例**:
```sql
SELECT * FROM users WHERE id = 1;

正确示例:

SELECT id, name, email FROM users WHERE id = 1;

**步骤 3:更新配置**

编辑 `rules/rules_config.json`:

```json
{
  "rule_sets": {
    "backend": { ... },
    "frontend": { ... },
    "sql": {
      "enabled": true,
      "file_patterns": ["**/*.sql"],
      "severity_threshold": "warning",
      "disabled_rules": []
    }
  }
}

步骤 4:更新 RulesLoader

autocoder/checker/rules_loader.py 中添加:

def get_applicable_rules(self, file_path: str) -> List[Rule]:
    """根据文件路径获取适用的规则"""
    ext = os.path.splitext(file_path)[1].lower()

    # 现有映射
    if ext == ".py":
        return self.load_rules("backend")
    elif ext in [".js", ".jsx", ".ts", ".tsx", ".vue"]:
        return self.load_rules("frontend")
    elif ext == ".sql":  # 新增
        return self.load_rules("sql")

    return []

2. 添加自定义报告格式

假设要添加 HTML 报告:

步骤 1:创建报告生成方法

autocoder/checker/report_generator.py 中添加:

def generate_html_report(
    self, results: List[FileCheckResult], report_dir: str
) -> None:
    """生成 HTML 格式报告"""
    # HTML 模板
    html_template = """
    <!DOCTYPE html>
    <html>
    <head>
        <title>代码检查报告</title>
        <style>
            /* CSS 样式 */
        </style>
    </head>
    <body>
        <h1>代码检查报告</h1>
        {content}
    </body>
    </html>
    """

    # 生成内容
    content = self._generate_html_content(results)

    # 保存文件
    html_path = os.path.join(report_dir, "report.html")
    with open(html_path, 'w', encoding='utf-8') as f:
        f.write(html_template.format(content=content))

步骤 2:调用生成方法

在插件中调用:

def _check_folder(self, args: str):
    # ... 检查逻辑 ...

    # 生成报告
    self.report_generator.generate_summary_report(results, report_dir)
    self.report_generator.generate_html_report(results, report_dir)  # 新增

3. 集成外部检查工具

假设要集成 Pylint:

步骤 1:创建工具包装器

# autocoder/checker/tools/pylint_wrapper.py

import subprocess
import json
from typing import List
from ..types import Issue, Severity

class PylintWrapper:
    def check(self, file_path: str) -> List[Issue]:
        """使用 Pylint 检查文件"""
        # 运行 Pylint
        result = subprocess.run(
            ["pylint", "--output-format=json", file_path],
            capture_output=True,
            text=True
        )

        # 解析结果
        pylint_issues = json.loads(result.stdout)

        # 转换为 Issue 对象
        issues = []
        for item in pylint_issues:
            issues.append(Issue(
                rule_id=f"pylint_{item['message-id']}",
                severity=self._map_severity(item['type']),
                line_start=item['line'],
                line_end=item['line'],
                description=item['message'],
                suggestion=item.get('hint', ''),
                code_snippet=''
            ))

        return issues

    def _map_severity(self, pylint_type: str) -> Severity:
        """映射 Pylint 严重程度"""
        mapping = {
            'error': Severity.ERROR,
            'warning': Severity.WARNING,
            'convention': Severity.INFO,
            'refactor': Severity.INFO,
        }
        return mapping.get(pylint_type, Severity.INFO)

步骤 2:集成到检查流程

core.py 中:

def check_file(self, file_path: str) -> FileCheckResult:
    """检查单个文件"""
    # LLM 检查
    llm_issues = self._check_with_llm(file_path)

    # Pylint 检查(可选)
    if self.enable_pylint and file_path.endswith('.py'):
        pylint = PylintWrapper()
        pylint_issues = pylint.check(file_path)
        llm_issues.extend(pylint_issues)

    # 合并结果
    merged_issues = self._merge_duplicate_issues(llm_issues)

    # ... 返回结果

API 参考

CodeChecker

class CodeChecker:
    def __init__(self, llm: byzerllm.ByzerLLM, args: AutoCoderArgs)

    def check_file(self, file_path: str) -> FileCheckResult
        """检查单个文件"""

    def check_files_concurrent(
        self, files: List[str], max_workers: int = 5
    ) -> Generator[FileCheckResult, None, None]
        """并发检查多个文件"""

    def check_code_chunk(
        self, code: str, rules: List[Rule]
    ) -> List[Issue]
        """检查代码块"""

RulesLoader

class RulesLoader:
    def __init__(self, rules_dir: str = "rules")

    def load_rules(self, rule_type: str) -> List[Rule]
        """加载指定类型的规则"""

    def get_applicable_rules(self, file_path: str) -> List[Rule]
        """根据文件路径获取适用的规则"""

    def reload_rules(self) -> None
        """重新加载所有规则"""

FileProcessor

class FileProcessor:
    def __init__(self, chunk_size: int = 4000, overlap: int = 200)

    def scan_files(self, path: str, filters: FileFilters) -> List[str]
        """扫描目录,返回符合条件的文件列表"""

    def chunk_file(self, file_path: str) -> List[CodeChunk]
        """将大文件分块"""

    def is_checkable(self, file_path: str) -> bool
        """判断文件是否可检查"""

ProgressTracker

class ProgressTracker:
    def __init__(self, state_dir: str = ".auto-coder/codecheck/progress")

    def start_check(self, files: List[str], config: Dict) -> str
        """开始新的检查,返回 check_id"""

    def mark_completed(self, check_id: str, file_path: str) -> None
        """标记文件已完成检查"""

    def get_remaining_files(self, check_id: str) -> List[str]
        """获取待检查的文件列表"""

    def save_state(self, check_id: str, state: CheckState) -> None
        """保存检查状态"""

    def load_state(self, check_id: str) -> Optional[CheckState]
        """加载检查状态"""

ReportGenerator

class ReportGenerator:
    def __init__(self, output_dir: str = "codecheck")

    def generate_file_report(
        self, result: FileCheckResult, report_dir: str
    ) -> None
        """生成单个文件的检查报告"""

    def generate_summary_report(
        self, results: List[FileCheckResult], report_dir: str
    ) -> None
        """生成汇总报告"""

测试指南

单元测试

测试文件位置tests/checker/

测试示例

# tests/checker/test_rules_loader.py

import pytest
from autocoder.checker.rules_loader import RulesLoader
from autocoder.checker.types import Rule, Severity

def test_load_backend_rules():
    """测试加载后端规则"""
    loader = RulesLoader()
    rules = loader.load_rules("backend")

    assert len(rules) > 0
    assert all(isinstance(r, Rule) for r in rules)
    assert all(r.id.startswith("backend_") for r in rules)

def test_get_applicable_rules_for_python():
    """测试 Python 文件规则选择"""
    loader = RulesLoader()
    rules = loader.get_applicable_rules("test.py")

    assert len(rules) > 0
    assert all(r.id.startswith("backend_") for r in rules)

def test_rule_caching():
    """测试规则缓存"""
    loader = RulesLoader()

    rules1 = loader.load_rules("backend")
    rules2 = loader.load_rules("backend")

    # 应该返回同一个对象(缓存)
    assert rules1 is rules2

运行测试

# 运行所有检查器测试
pytest tests/checker/ -v

# 运行特定测试文件
pytest tests/checker/test_rules_loader.py -v

# 查看覆盖率
pytest tests/checker/ --cov=autocoder/checker --cov-report=html

集成测试

# tests/checker/test_integration.py

def test_full_check_workflow():
    """测试完整的检查流程"""
    # 1. 创建临时测试文件
    test_file = "test_code.py"
    with open(test_file, 'w') as f:
        f.write("""
def test_function():
    if True:
        if True:
            if True:
                if True:
                    pass  # 嵌套过深
        """)

    try:
        # 2. 初始化检查器
        checker = CodeChecker(mock_llm, mock_args)

        # 3. 执行检查
        result = checker.check_file(test_file)

        # 4. 验证结果
        assert result.status == "success"
        assert len(result.issues) > 0

        # 5. 验证报告生成
        report_gen = ReportGenerator()
        report_gen.generate_file_report(result, "test_reports")

        assert os.path.exists("test_reports/files/test_code_py.json")
        assert os.path.exists("test_reports/files/test_code_py.md")

    finally:
        # 清理
        os.remove(test_file)
        shutil.rmtree("test_reports")

Mock 测试

from unittest.mock import Mock, patch

def test_check_with_mock_llm():
    """使用 Mock LLM 测试"""
    # Mock LLM 返回
    mock_llm = Mock()
    mock_llm.chat_oai.return_value = [Mock(output="""
    ```json
    [
        {
            "rule_id": "backend_001",
            "severity": "error",
            "line_start": 10,
            "line_end": 15,
            "description": "测试问题",
            "suggestion": "测试建议"
        }
    ]
    ```
    """)]

    # 执行检查
    checker = CodeChecker(mock_llm, mock_args)
    result = checker.check_file("test.py")

    # 验证
    assert result.status == "success"
    assert len(result.issues) == 1
    assert result.issues[0].rule_id == "backend_001"

性能优化建议

1. 规则缓存

# 使用 LRU 缓存
from functools import lru_cache

class RulesLoader:
    @lru_cache(maxsize=10)
    def load_rules(self, rule_type: str) -> List[Rule]:
        # 加载逻辑
        pass

2. 并发优化

# 动态调整并发数
def get_optimal_workers(file_count: int) -> int:
    if file_count < 10:
        return 2
    elif file_count < 50:
        return 5
    else:
        return 10

3. 增量检查

# 只检查修改的文件
def get_changed_files() -> List[str]:
    result = subprocess.run(
        ["git", "diff", "--name-only", "HEAD"],
        capture_output=True,
        text=True
    )
    return result.stdout.strip().split('\n')

常见问题

Q1: 如何调试 LLM Prompt?

A: 启用日志记录:

import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("autocoder.checker")

# 查看 LLM 请求和响应
logger.debug(f"LLM Request: {prompt}")
logger.debug(f"LLM Response: {response}")

Q2: 如何优化检查速度?

A:

  1. 增加并发数
  2. 减少规则数量
  3. 使用更快的模型
  4. 实现结果缓存

Q3: 如何处理检查结果不准确?

A:

  1. 优化 Prompt 模板
  2. 添加更多示例到规则
  3. 使用更强的模型
  4. 结合静态分析工具

贡献指南

欢迎贡献代码和改进建议!

开发流程

  1. Fork 项目
  2. 创建特性分支
  3. 编写代码和测试
  4. 提交 Pull Request

代码规范

  • 遵循 PEP 8
  • 添加类型注解
  • 编写文档字符串
  • 添加单元测试

修复记录

2025-10-11: 修复模型获取逻辑

问题描述: 用户在使用 /check /folder 命令时遇到错误:

CodeChecker 初始化失败: Failed to create LLM instance for models: deepseek/deepseek-chat
- Model 'deepseek/deepseek-chat' not found

问题原因: 在 code_checker_plugin.py_ensure_checker() 方法中(第103行),代码硬编码了一个默认模型:

model_name = conf.get("model", "deepseek/deepseek-chat")

当配置中没有 "model" 字段时,会使用这个不存在的默认模型,导致初始化失败。

修复方案

  1. 智能模型选择:按优先级选择模型

    • 优先使用 chat_model(chat 模式专用模型)
    • 其次使用 model(通用模型)
    • 最后从 LLMManager 中自动选择第一个有 API key 的可用模型
  2. 友好的错误提示

    • 当完全没有可用模型时,提供清晰的配置指导
    • 当模型无法初始化时,提示可能的原因和解决方法

修改文件

  • autocoder/plugins/code_checker_plugin.py (第82-139行)

修改内容

def _ensure_checker(self):
    # ... 省略部分代码 ...

    # 智能获取模型配置
    # 1. 优先使用 chat_model(chat 模式专用)
    # 2. 其次使用 model(通用模型)
    # 3. 最后尝试获取第一个可用模型
    model_name = conf.get("chat_model") or conf.get("model")

    if not model_name:
        # 如果配置中没有模型,尝试从 LLMManager 获取第一个有 API key 的模型
        llm_manager = LLMManager()
        all_models = llm_manager.get_all_models()

        # 查找第一个有 API key 的模型
        for name, model in all_models.items():
            if llm_manager.has_key(name):
                model_name = name
                logger.info(f"[{self.name}] 配置中未指定模型,自动选择: {model_name}")
                break

        if not model_name:
            raise RuntimeError(
                "未配置模型,且未找到可用的模型\n"
                "请使用以下方式之一配置模型:\n"
                "1. /models /add <model_name> <api_key> - 添加并激活模型\n"
                "2. /config model <model_name> - 设置当前使用的模型"
            )

    # ... 省略后续代码 ...

测试验证

  • 场景1:配置中有 chat_model → 使用 chat_model
  • 场景2:配置中只有 model → 使用 model
  • 场景3:配置中都没有,但有已激活的模型 → 自动选择第一个有 API key 的模型
  • 场景4:没有任何可用模型 → 显示友好的错误提示

影响范围

  • 仅影响 CodeChecker 的初始化逻辑
  • 不影响现有功能
  • 向后兼容

相关文件

  • autocoder/plugins/code_checker_plugin.py
  • autocoder/checker/core.py

提交信息

fix(checker): 智能获取当前激活的模型

- 优先使用 chat_model,其次 model,最后自动选择
- 移除硬编码的默认模型 "deepseek/deepseek-chat"
- 添加友好的错误提示和配置指导
- 修复用户使用 /check 命令时的模型初始化失败问题

2025-10-11: 添加规则文件自动初始化功能

问题描述: 用户在项目目录执行 /check /folder 命令时,遇到大量文件检查失败(102个失败,0个成功),原因是当前目录没有规则文件。

解决方案: 在 RulesLoader 中添加自动初始化功能,当规则文件不存在时自动从模板复制。

主要改进

  1. 智能模板查找(按优先级):

    • 构造函数参数 template_rules_dir
    • 环境变量 CODE_CHECKER_TEMPLATE_DIR
    • 默认位置:项目根目录 rules/
  2. 自动初始化流程

    • 检测规则文件不存在
    • 查找模板目录
    • 验证模板文件完整性
    • 复制规则文件到当前目录
    • 显示友好提示
  3. 用户友好的提示

✨ 检测到当前目录没有规则文件
📋 正在从模板自动创建规则文件...
   ✓ backend_rules.md (63条后端规则)
   ✓ frontend_rules.md (105条前端规则)
   ✓ rules_config.json (配置文件)

✅ 规则文件初始化成功!
   规则目录: /path/to/current/rules

修改文件

  • autocoder/checker/rules_loader.py
    • 修改 __init__() 添加 template_rules_dirauto_init 参数
    • 修改 load_rules() 添加自动初始化逻辑
    • 新增 _get_template_dir() 方法
    • 新增 _auto_initialize_rules() 方法

配置方式

方式1:使用默认行为(推荐)

/check /folder  # 自动从项目根目录的 rules/ 复制

方式2:通过环境变量指定

export CODE_CHECKER_TEMPLATE_DIR=/path/to/template/rules
/check /folder

方式3:禁用自动初始化

loader = RulesLoader(auto_init=False)

特性

  • ✅ 默认启用,无需手动配置
  • ✅ 向后兼容
  • ✅ 友好的错误提示
  • ✅ 支持多种模板路径配置
  • ✅ 防止重复初始化

提交信息

feat(checker): 添加规则文件自动初始化功能

- 当检测到没有规则文件时自动从模板复制
- 支持多种方式指定模板目录(参数/环境变量/默认位置)
- 提供友好的用户提示和错误指导
- 解决用户在项目目录执行检查时规则文件缺失的问题
- 默认启用,向后兼容

修复问题:用户执行 /check /folder 时 102 个文件失败

2025-10-11: 修复 LLM 调用超时导致最后文件无法处理的问题

问题描述: 在使用 /check /folder/check /resume 命令进行代码检查时,最后一个文件(如 DictItemServiceImpl.java)无法完成检查,表现为:

  1. 进度条显示 100% 但检查未完成
  2. 文件检查一直卡住,长时间无响应(10+ 分钟)
  3. Resume 后仍然卡在同一个文件

问题根因: 通过分析日志发现,文件检查卡在 LLM API 调用阶段:

  • DictItemServiceImpl.java 在 03:41:57 开始检查,但从未完成
  • Resume 时(03:46:57)再次开始检查,依然卡住
  • 从日志来看,check_code_chunk() 方法中的 self.llm.chat_oai() 调用没有超时机制,导致:
    • API 调用超时或网络中断时,程序一直等待
    • 无法继续检查下一个文件或下一个 chunk
    • Resume 功能也无法解决问题

解决方案

  1. 为 LLM 调用添加超时机制

    • 使用 ThreadPoolExecutor 包装 LLM 调用
    • 设置 180 秒超时时间
    • 超时后返回空结果,继续处理
  2. 增强异常处理和日志

    • 添加详细的 LLM 调用时间记录
    • 记录每个 chunk 的检查进度
    • 统计超时的 chunk 数量
  3. 改进文件级错误处理

    • 确保即使某个 chunk 超时,也能继续检查其他 chunks
    • 记录 chunk 级别的失败情况

修改内容

autocoder/checker/core.py 中:

# 1. 导入 TimeoutError
from concurrent.futures import ThreadPoolExecutor, as_completed, TimeoutError

# 2. 修改 check_code_chunk() 方法,添加 timeout 参数
def check_code_chunk(
    self, code: str, rules: List[Rule], timeout: int = 180
) -> List[Issue]:
    """
    检查代码块

    Args:
        code: 代码内容(带行号)
        rules: 适用的规则列表
        timeout: LLM 调用超时时间(秒),默认 180 秒
    """
    try:
        # ... 准备工作 ...

        # 使用 ThreadPoolExecutor 实现超时
        with ThreadPoolExecutor(max_workers=1) as executor:
            future = executor.submit(self._call_llm, conversations)
            try:
                response = future.result(timeout=timeout)
            except TimeoutError:
                logger.error(f"LLM 调用超时({timeout}秒),跳过此代码块")
                return []
            except Exception as e:
                logger.error(f"LLM 调用失败: {e}", exc_info=True)
                return []

        # ... 处理响应 ...

# 3. 新增 _call_llm() 辅助方法
def _call_llm(self, conversations: List[Dict[str, str]]) -> Any:
    """
    调用 LLM(内部方法,用于支持超时)

    记录调用开始/结束时间,便于排查问题
    """
    start_time = datetime.now()
    logger.debug(f"开始 LLM 调用: {start_time.isoformat()}")

    try:
        response = self.llm.chat_oai(conversations=conversations)

        end_time = datetime.now()
        duration = (end_time - start_time).total_seconds()
        logger.debug(f"LLM 调用完成,耗时: {duration:.2f}秒")

        return response
    except Exception as e:
        end_time = datetime.now()
        duration = (end_time - start_time).total_seconds()
        logger.error(f"LLM 调用异常(耗时 {duration:.2f}秒): {e}", exc_info=True)
        raise

# 4. 改进 check_file() 中的 chunk 处理
for chunk in chunks:
    logger.info(
        f"检查 chunk {chunk.chunk_index + 1}/{len(chunks)}: "
        f"行 {chunk.start_line}-{chunk.end_line}"
    )

    try:
        issues = self.check_code_chunk(chunk.content, rules)
        # ... 处理结果 ...
        logger.info(f"Chunk {chunk.chunk_index + 1} 完成,发现 {len(issues)} 个问题")
    except Exception as e:
        logger.error(f"检查 chunk {chunk.chunk_index} 时发生异常: {e}", exc_info=True)
        chunk_timeout_count += 1
        continue  # 继续检查下一个 chunk

# 记录超时情况
if chunk_timeout_count > 0:
    logger.warning(
        f"文件 {file_path}{chunk_timeout_count}/{len(chunks)} "
        f"个 chunk 检查失败或超时"
    )

测试验证

  • ✅ 代码语法验证通过
  • ✅ 模块导入成功
  • ✅ 所有单元测试通过(18 个测试)

修复效果

  1. LLM 调用超时后自动失败并继续:不再卡住整个检查流程
  2. Resume 功能正常:失败的文件会被标记为已处理,不会重复卡住
  3. 明确的超时日志:便于用户了解问题原因
  4. 部分成功处理:如果文件有多个 chunks,部分超时不影响其他 chunks

影响范围

  • 修改了 CodeChecker.check_code_chunk() 方法签名(新增可选参数)
  • 新增了 CodeChecker._call_llm() 辅助方法
  • 向后兼容:默认超时 180 秒,可通过参数调整

相关文件

  • autocoder/checker/core.py (第 19, 257-336 行)

提交信息

fix(checker): 修复 LLM 调用超时导致最后文件无法处理的问题

问题描述:
- 检查时最后一个文件一直卡住,无法完成
- Resume 后仍然卡在同一个文件
- 原因:LLM API 调用没有超时机制

解决方案:
1. 为 LLM 调用添加 180 秒超时机制
2. 超时后返回空结果,继续处理下一个 chunk/文件
3. 增强日志记录,显示调用耗时和超时信息
4. 改进 chunk 级异常处理,确保部分失败不影响整体

测试验证:
- 所有单元测试通过(18/18)
- 代码语法和导入验证通过

影响:向后兼容,默认超时 180 秒

2025-10-11: 修复文件检查超时和报告生成问题

问题描述: 在 /projects/codecheck 项目的检查中发现两个问题:

  1. 最后一个文件卡住不动DictItemServiceImpl.java (556行) 在并发检查时无法完成
  2. 没有生成检查报告:即使已完成140个文件,也没有生成任何报告

问题分析

  1. 卡住原因

    • 虽然之前添加了 chunk 级别的 180 秒超时,但没有文件级别的超时保护
    • DictItemServiceImpl.java 虽然分成了多个 chunks,但所有 chunks 累计时间超过10分钟
    • 并发检查时,该文件的 check_file() 方法一直阻塞在线程池中
    • check_files_concurrent()future.result() 调用没有超时,导致永久等待
  2. 报告缺失原因

    • 并发检查使用生成器模式,只有当所有文件都返回结果时才会退出循环
    • 由于最后一个文件卡住,生成器永远无法完成迭代
    • 插件的 _check_folder() 方法在第 177 行的 for result in ... 循环被阻塞
    • 报告生成代码(第 386-394 行)永远无法执行

解决方案

1. 为单个文件添加总超时保护

autocoder/checker/core.py 中修改 check_file() 方法:

def check_file(self, file_path: str, file_timeout: int = 600) -> FileCheckResult:
    """
    检查单个文件

    Args:
        file_path: 文件路径
        file_timeout: 单个文件检查的最大超时时间(秒),默认 600 秒(10分钟)
    """
    logger.info(f"开始检查文件: {file_path} (超时: {file_timeout}秒)")

    # 使用 ThreadPoolExecutor 实现文件级超时
    with ThreadPoolExecutor(max_workers=1) as executor:
        future = executor.submit(self._check_file_impl, file_path)

        try:
            result = future.result(timeout=file_timeout)
            return result

        except TimeoutError:
            logger.error(f"文件 {file_path} 检查超时({file_timeout}秒)")
            return FileCheckResult(
                file_path=file_path,
                check_time=datetime.now().isoformat(),
                issues=[],
                error_count=0,
                warning_count=0,
                info_count=0,
                status="timeout",  # 新增状态
                error_message=f"文件检查超时({file_timeout}秒)"
            )

def _check_file_impl(self, file_path: str) -> FileCheckResult:
    """检查单个文件的内部实现(用于支持超时)"""
    # 原 check_file() 的实现逻辑移到这里
    ...

关键改进

  • 将原有的 check_file() 逻辑拆分为 _check_file_impl()
  • ThreadPoolExecutor 包装,实现文件级超时控制
  • 默认 600 秒超时(可配置)
  • 超时时返回 status="timeout" 的结果,而不是阻塞

2. 为并发检查传递超时参数

修改 check_files_concurrent() 方法:

def check_files_concurrent(
    self, files: List[str], max_workers: int = 5, file_timeout: int = 600
) -> Generator[FileCheckResult, None, None]:
    """
    并发检查多个文件

    Args:
        file_timeout: 单个文件检查的最大超时时间(秒),默认 600 秒(10分钟)
    """
    logger.info(f"开始并发检查 {len(files)} 个文件 (workers={max_workers}, file_timeout={file_timeout}秒)")

    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        # 提交所有任务(传递 file_timeout 参数)
        future_to_file = {
            executor.submit(self.check_file, file_path, file_timeout): file_path
            for file_path in files
        }

        # 按完成顺序返回结果
        for future in as_completed(future_to_file):
            file_path = future_to_file[future]
            try:
                result = future.result()  # 不需要额外 timeout,check_file 内部已有
                yield result
            except Exception as exc:
                # ... 异常处理

关键改进

  • file_timeout 参数传递给 check_file()
  • 每个文件都有独立的超时控制
  • future.result() 不需要设置 timeout,因为 check_file() 内部已经有超时机制

3. 确保即使检查未完成也能生成报告

修改 autocoder/plugins/code_checker_plugin.py_check_folder() 方法:

def _check_folder(self, args: str) -> None:
    # ... 前置代码 ...

    results = []
    check_interrupted = False

    try:
        with Progress(...) as progress:
            for result in self.checker.check_files_concurrent(files, max_workers=workers):
                results.append(result)
                # ... 更新进度 ...

    except KeyboardInterrupt:
        check_interrupted = True
        # ... 处理中断 ...

    finally:
        # 确保即使中断或出错也生成部分报告
        if results:
            logger.info(f"生成部分报告,已完成 {len(results)} 个文件")

            # 如果是正常完成,标记状态
            if not check_interrupted:
                state = self.progress_tracker.load_state(check_id)
                if state:
                    state.status = "completed"
                    self.progress_tracker.save_state(check_id, state)

            # 生成报告
            report_dir = self._create_report_dir(check_id)

            # 生成单文件报告
            for result in results:
                self.report_generator.generate_file_report(result, report_dir)

            # 生成汇总报告
            self.report_generator.generate_summary_report(results, report_dir)

            # 显示汇总
            if check_interrupted:
                print(f"\n📄 已生成部分报告 ({len(results)}/{len(files)} 个文件)")
                print(f"   报告位置: {report_dir}/")
                print(f"\n💡 使用以下命令恢复检查:")
                print(f"   /check /resume {check_id}\n")
            else:
                self._show_batch_summary(results, report_dir)

关键改进

  • 使用 try-finally 结构确保报告一定会生成
  • 即使中断或超时,已完成的文件也会生成报告
  • 区分正常完成和中断两种情况的提示

4. 处理 timeout 状态的统计显示

修改 _show_batch_summary() 方法:

def _show_batch_summary(self, results: List, report_dir: str) -> None:
    # 统计
    total_files = len(results)
    checked_files = len([r for r in results if r.status == "success"])
    skipped_files = len([r for r in results if r.status == "skipped"])
    failed_files = len([r for r in results if r.status == "failed"])
    timeout_files = len([r for r in results if r.status == "timeout"])  # 新增

    print(f"检查文件: {total_files}")
    print(f"├─ ✅ 成功: {checked_files}")
    print(f"├─ ⏭️  跳过: {skipped_files}")
    print(f"├─ ⏱️  超时: {timeout_files}")  # 新增
    print(f"└─ ❌ 失败: {failed_files}")

同时在单文件检查结果显示中添加:

elif result.status == "timeout":
    print(f"⏱️  文件检查超时: {file_path}")
    print(f"   错误: {result.error_message}")

修改文件清单

  1. autocoder/checker/core.py

    • 修改 check_file() 添加文件级超时
    • 新增 _check_file_impl() 实现方法
    • 修改 check_files_concurrent() 传递超时参数
  2. autocoder/plugins/code_checker_plugin.py

    • 修改 _check_folder() 使用 try-finally 确保报告生成
    • 修改 _show_batch_summary() 统计 timeout 状态
    • 修改 _check_file() 显示 timeout 状态

测试验证

  • ✅ 所有单元测试通过 (4/4 check_file 相关测试)
  • ✅ 代码语法验证通过
  • ✅ 向后兼容:默认超时 600 秒

预期效果

  1. 不再卡住:文件超时后自动返回超时结果,继续检查下一个文件
  2. 总是生成报告:即使有文件超时或中断,已完成的文件也会生成报告
  3. 明确的超时信息:用户可以看到哪些文件超时,超时时间是多少
  4. 支持自定义超时:可以根据项目大小调整 file_timeout 参数

配置建议

  • 小文件(< 200 行): 180 秒足够
  • 中等文件(200-1000 行): 300-600 秒
  • 大文件(1000+ 行): 600-900 秒
  • 特大文件(2000+ 行): 可考虑拆分或增加到 1200 秒

提交信息

fix(checker): 修复文件检查超时和报告无法生成的问题

问题描述:
1. 大文件(如 DictItemServiceImpl.java)在并发检查时卡住不动
2. 由于一个文件卡住,导致整个检查无法完成,报告无法生成

根本原因:
1. 缺少文件级总超时保护(只有 chunk 级 180 秒超时)
2. 报告生成代码在检查完成之后,被阻塞无法执行

解决方案:
1. 为单个文件添加 600 秒总超时保护
2. 将 check_file() 拆分为带超时的外层和实现的内层
3. 并发检查传递超时参数给每个文件
4. 使用 try-finally 确保即使部分失败也能生成报告
5. 添加 timeout 状态的统计和显示

修改文件:
- autocoder/checker/core.py (文件超时控制)
- autocoder/plugins/code_checker_plugin.py (报告生成保护)

测试: 所有单元测试通过, 向后兼容

最后更新:2025-10-11 文档版本:1.0.4 作者:Claude AI


2025-10-12: 提升检查稳定性与可配置能力

更新内容概览

  • ✅ 默认启用确定性 LLM 配置:temperature=0.0top_p=1.0seed=42
  • ✅ 支持通过 AutoCoderArgs、插件配置或环境变量覆盖 LLM 相关参数
  • ✅ 文件分块结果缓存 + 默认 20000 token 分块阈值,可配置 overlap multiplier,保证多轮扫描分块一致
  • ✅ 支持多次 LLM 调用并按共识保留问题,显著提升结果稳定性
  • ✅ 合并问题时容忍 ±1 行偏差,并在信息冲突时保留描述更完整的版本
  • ✅ 新增稳定性回归测试 tests/stability/test_deterministic_results.py

配置方式

配置项 默认值 覆盖途径 说明
温度 temperature 0.0 AutoCoderArgs(checker_llm_temperature=...)
/plugins code_checker 配置 llm_temperature
环境变量 CODECHECKER_LLM_TEMPERATURE
控制输出随机性,推荐 0.0~0.3
核采样 top_p 1.0 同上,字段名 checker_llm_top_p / llm_top_p / CODECHECKER_LLM_TOP_P 1.0 表示禁用核采样
Seed seed 42 同上,字段名 checker_llm_seed / llm_seed / CODECHECKER_LLM_SEED 设为 -1 可显式关闭
额外 LLM 参数 checker_llm_config / checker.llm 节配置字典 支持传入 presence_penalty 等模型特定参数
Chunk token limit 20000 checker_chunk_token_limit / chunk_token_limit / CODECHECKER_CHUNK_TOKEN_LIMIT 达到阈值才拆分 chunk,避免对中小文件分块
Chunk overlap multiplier FileProcessor.overlap 基础值 checker_chunk_overlap_multiplier / chunk_overlap_multiplier / CODECHECKER_CHUNK_OVERLAP_MULTIPLIER 用于放大 overlap,增强分块上下文一致性
LLM repeat 1 checker_llm_repeat / llm_repeat / CODECHECKER_LLM_REPEAT 对同一 chunk 重复检查多次以取交集或多数票
Consensus ratio 1.0 checker_llm_consensus_ratio / llm_consensus / CODECHECKER_LLM_CONSENSUS 通过出现比例控制保留问题(1.0=全交集)

提示:插件配置示例

{
  "checker": {
    "llm": {
      "presence_penalty": 0.1
    },
    "llm_temperature": 0.1,
    "llm_seed": 99,
    "chunk_overlap_multiplier": 2.0
  }
}

开发注意事项

  • CodeChecker.llm_config 会在初始化时根据上述优先级构建,可通过日志查看实际生效的参数。
  • FileProcessor 现在会缓存分块结果(按文件 mtime + size),命中缓存时返回深拷贝,避免多线程下共享状态。
  • 合并问题时,rule_id 相同且行号差 ≤1 会视作同一问题;最终行号范围取最小起点与最大终点。
  • 稳定性测试需通过 pytest -m stability(或运行全量测试)确保配置调整不会破坏确定性。

版本号:1.0.5


2025-10-12: 修复 /check /file 命令中 'options' 未定义的错误

问题描述: 用户在使用 /check /file ./DictItemServiceImpl.java 命令检查单个文件时,遇到错误:

❌ 检查过程出错: name 'options' is not defined

问题原因: 在 autocoder/plugins/code_checker_plugin.py_check_file() 方法中(第 423-426 行):

  1. 第 401 行定义了变量 common_options = self._parse_common_options(option_tokens)
  2. 第 423-426 行错误地使用了未定义的 options 变量
  3. 第 429 行又调用了一次 _apply_checker_options(common_options),使用了正确的变量名

这是一个变量名错误,导致运行时报 NameError。同时,第 423-426 行和第 429 行是重复调用。

修复方案: 删除第 423-426 行的错误代码,只保留第 429 行的正确调用。

修改前代码

# 第 401 行
common_options = self._parse_common_options(option_tokens)

try:
    # 确保 checker 已初始化
    self._ensure_checker()
    self._apply_checker_options({
        "repeat": options.get("repeat"),        # ❌ options 未定义
        "consensus": options.get("consensus"),  # ❌ options 未定义
    })

    # 应用共识参数
    self._apply_checker_options(common_options)  # ✅ 正确

修改后代码

# 第 401 行
common_options = self._parse_common_options(option_tokens)

try:
    # 确保 checker 已初始化
    self._ensure_checker()

    # 应用共识参数
    self._apply_checker_options(common_options)  # ✅ 正确

修改文件

  • autocoder/plugins/code_checker_plugin.py (删除第 423-426 行)

测试验证

  • ✅ Python 语法检查通过:python3 -m py_compile autocoder/plugins/code_checker_plugin.py
  • ✅ 代码逻辑验证正确

代码逻辑流程(修复后):

  1. 第 401 行:解析参数 → common_options
  2. 第 422 行:初始化 checker
  3. 第 425 行:应用参数 → 使用 common_options

影响范围

  • 仅影响 /check /file 命令的参数处理逻辑
  • 修复后命令可以正常运行
  • 不影响其他功能
  • 向后兼容

提交信息

fix(plugin): 修复 /check /file 命令中 'options' 未定义的错误

问题:
- /check /file 命令报错: name 'options' is not defined
- 第 423-426 行使用了未定义的 'options' 变量
- 实际定义的变量名是 'common_options' (第 401 行)
- 存在重复调用 _apply_checker_options

修复:
- 删除第 423-426 行错误的重复代码
- 保留第 429 行使用 common_options 的正确调用
- 代码逻辑流程现在正确:
  1. 解析参数 → common_options
  2. 初始化 checker
  3. 应用参数 → 使用 common_options

测试:
- Python 语法检查通过
- 代码逻辑验证正确

相关文件:
- autocoder/plugins/code_checker_plugin.py

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Git 提交951c94f


2025-10-13: 将默认 consensus 阈值从 1.0 调整为 0.34

问题描述: 经过实际测试发现,当 consensus=1.0(要求完全一致)时,多次 LLM 调用可能出现"集体放水"现象,导致本应发现的问题被漏报。例如:

  • 3 次调用分别返回 0、0、5 个问题
  • 因为要求完全一致(consensus=1.0),最终只保留 0 个问题
  • 这导致 5 个实际存在的问题被漏报

解决方案: 将默认 consensus 阈值从 1.0 调整为 0.34(3次中至少1次发现即保留)

计算逻辑

  • 默认 llm_repeat=3(重复调用 3 次)
  • threshold = max(1, ceil(0.34 * 3)) = max(1, 1) = 1
  • 即:问题在 3 次调用中至少出现 1 次,就会被保留
  • 这在保持准确性的同时,显著降低漏报率

主要修改

  1. 核心模块 (autocoder/checker/core.py):

    • 第 88 行:将 DEFAULT_LLM_CONSENSUS_RATIO1.0 改为 0.34
    • 增强了一致性检查逻辑(_aggregate_attempt_results 方法):
      • 计算变异系数(CV)检测输出不一致
      • 检测"集体放水"现象(部分调用返回 0,部分返回正常数量)
      • 发出警告提示用户检查 Prompt
    • 改进了 Prompt(check_code_prompt 方法):
      • 强调核心原则:"宁可误报,不可漏报"
      • 要求严格执行规则,不要因为"看起来还行"就放宽标准
      • 提供错误示例和正确示例
      • 强调一致性要求
    • 优化日志格式,使用 f-string
  2. 插件配置 (autocoder/plugins/code_checker_plugin.py):

    • 第 56 行:将插件默认值 consensus1.0 改为 0.34
    • 确保插件与核心模块的默认值一致

效果对比

场景 consensus=1.0 consensus=0.34 说明
3次调用:0, 0, 0 保留 0 个 ✅ 保留 0 个 ✅ 真的没问题
3次调用:5, 5, 5 保留 5 个 ✅ 保留 5 个 ✅ 完全一致
3次调用:0, 0, 5 保留 0 个 ❌ 保留 5 个 ✅ 防止漏报
3次调用:3, 4, 5 保留 3 个(交集) 保留全部 ✅ 包含所有发现
3次调用:2, 8, 3 保留 0 个 ❌ 保留全部 ✅ 可能有假阳,但不漏报

新增功能

  1. 一致性监测

    • 自动计算变异系数(CV = std / mean)
    • CV > 0.5 时发出警告
    • 检测"集体放水"现象并警告
  2. Prompt 增强

    • 明确"宁可误报,不可漏报"原则
    • 列举常见错误和正确示例
    • 强调客观量化判断(如行数、嵌套层数)

使用建议

  1. 默认配置(推荐):

    /check /file test.py
    # 使用默认: repeat=3, consensus=0.34
  2. 完全一致模式(严格模式,可能漏报):

    /check /file test.py /repeat 3 /consensus 1.0
  3. 单次检查(最快,但不稳定):

    /check /file test.py /repeat 1
  4. 高覆盖模式(最全面,可能有假阳):

    /check /file test.py /repeat 5 /consensus 0.2

配置方式

通过插件配置文件设置默认值:

{
  "checker": {
    "defaults": {
      "repeat": 3,
      "consensus": 0.34
    }
  }
}

或通过命令设置:

/check /config /repeat 3 /consensus 0.34

影响范围

  • ✅ 向后兼容:不会破坏现有代码
  • ✅ 默认行为变更:检查更严格,覆盖更全面
  • ✅ 可通过参数覆盖:用户可自行选择平衡点
  • ✅ 性能影响:默认 3 次调用,检查时间约增加 2 倍(可接受)

相关文件

  • autocoder/checker/core.py (第 88, 184-256, 928-1022 行)
  • autocoder/plugins/code_checker_plugin.py (第 56 行)

提交信息:Git commit e22dc56

测试验证

  • ✅ 所有单元测试通过
  • ✅ 实际代码检查验证通过
  • ✅ 一致性监测正常工作

最后更新:2025-10-13 文档版本:1.0.7 作者:Claude AI