diff --git a/repair_agent/.env.example b/repair_agent/.env.example index 8303705..ddaafcb 100644 --- a/repair_agent/.env.example +++ b/repair_agent/.env.example @@ -16,8 +16,9 @@ GITHUB_REPO_RTC_WEB= GITHUB_REPO_AIRHUB_APP= # 项目路径映射 (project_id -> 本地路径) -PATH_rtc_backend=/Users/maidong/Desktop/zyc/qy_gitlab/rtc_backend -PATH_rtc_web=/Users/maidong/Desktop/zyc/qy_gitlab/rtc_web +PATH_RTC_BACKEND=/Users/maidong/Desktop/zyc/qy_gitlab/rtc_backend +PATH_RTC_WEB=/Users/maidong/Desktop/zyc/qy_gitlab/rtc_web +PATH_AIRHUB_APP=/Users/maidong/Desktop/zyc/qiyuan_gitea/rtc_prd/airhub_app # 安全配置 MAX_RETRY_COUNT=3 diff --git a/repair_agent/agent/claude_service.py b/repair_agent/agent/claude_service.py index 1ea1b50..f1468a4 100644 --- a/repair_agent/agent/claude_service.py +++ b/repair_agent/agent/claude_service.py @@ -20,16 +20,17 @@ class ClaudeService: self, prompt: str, cwd: str, - tools: str = "Edit,Read,Write", + allowed_tools: Optional[str] = None, ) -> tuple[bool, str]: """ 执行 Claude CLI 命令 - + Args: prompt: 提示词 cwd: 工作目录 - tools: 可用工具 - + allowed_tools: 可选,限制可用工具(逗号分隔)。 + 为 None 时配合 --dangerously-skip-permissions 允许所有工具。 + Returns: (成功与否, 输出内容) """ @@ -37,12 +38,15 @@ class ClaudeService: cmd = [ self.cli_path, "-p", prompt, - "--tools", tools, "--dangerously-skip-permissions", ] - - logger.debug(f"执行 Claude CLI: {' '.join(cmd[:3])}...") - + + if allowed_tools: + cmd.extend(["--allowedTools", allowed_tools]) + + logger.debug(f"执行 Claude CLI (cwd={cwd})") + logger.debug(f"Prompt 长度: {len(prompt)} 字符") + result = subprocess.run( cmd, cwd=cwd, @@ -50,16 +54,17 @@ class ClaudeService: text=True, timeout=self.timeout, ) - + output = result.stdout + result.stderr - + if result.returncode == 0: logger.info("Claude CLI 执行成功") + logger.debug(f"输出前 500 字符: {output[:500]}") return True, output.strip() else: - logger.error(f"Claude CLI 执行失败: {output}") + logger.error(f"Claude CLI 执行失败 (code={result.returncode}): {output[:500]}") return False, output.strip() - + except subprocess.TimeoutExpired: logger.error(f"Claude CLI 超时 ({self.timeout}秒)") return False, "执行超时" @@ -77,39 +82,44 @@ class ClaudeService: ) -> tuple[bool, str]: """ 批量修复多个 Bug - + Args: bugs: Bug 列表 project_path: 项目路径 - + Returns: (成功与否, Claude 输出) """ if not bugs: return False, "没有需要修复的 Bug" - + # 构造批量修复 Prompt prompt_parts = [ - f"请修复以下 {len(bugs)} 个 Bug:", + f"你是一个自动化 Bug 修复代理。请直接修复以下 {len(bugs)} 个 Bug。", + "", + "重要:你必须使用工具(Read、Edit、Write 等)直接修改源代码文件来修复 Bug。", + "不要只是分析问题或给出建议,你需要实际定位文件并编辑代码。", "", ] - + for bug in bugs: prompt_parts.append(bug.format_for_prompt()) prompt_parts.append("") - + prompt_parts.extend([ "## 修复要求", - "1. 依次修复上述所有 Bug", - "2. 每个 Bug 只做最小必要的改动", - "3. 确保不破坏现有功能", - "4. 修复完成后简要说明每个 Bug 的修复方式", + "1. 先用 Grep/Glob 定位相关源代码文件", + "2. 用 Read 读取文件内容,理解上下文", + "3. 用 Edit 或 Write 直接修改代码来修复 Bug", + "4. 每个 Bug 只做最小必要的改动", + "5. 确保不破坏现有功能", + "6. 修复完成后简要说明每个 Bug 的修复方式", "", - "请开始修复。", + "请立即开始修复,直接编辑文件。", ]) - + prompt = "\n".join(prompt_parts) - + logger.info(f"开始批量修复 {len(bugs)} 个 Bug...") return self.execute_prompt(prompt, project_path) @@ -135,7 +145,7 @@ class ClaudeService: 3. 可能影响的其他文件 """ - return self.execute_prompt(prompt, project_path, tools="Read") + return self.execute_prompt(prompt, project_path, allowed_tools="Read,Grep,Glob") def review_changes(self, diff: str, project_path: str) -> tuple[bool, str]: """ @@ -164,4 +174,4 @@ class ClaudeService: 如果审核通过,回复 "APPROVED"。否则说明问题。 """ - return self.execute_prompt(prompt, project_path, tools="Read") + return self.execute_prompt(prompt, project_path, allowed_tools="Read,Grep,Glob") diff --git a/repair_agent/agent/scheduler.py b/repair_agent/agent/scheduler.py index f030c9c..57d1380 100644 --- a/repair_agent/agent/scheduler.py +++ b/repair_agent/agent/scheduler.py @@ -88,15 +88,12 @@ class RepairScheduler: try: for project_id in self.projects: bugs = task_manager.fetch_pending_bugs(project_id) - # 也拉取 UI 触发的 PENDING_FIX 状态 - pending_bugs = self._fetch_pending_fix_bugs(task_manager, project_id) - all_bugs = bugs + pending_bugs - if not all_bugs: - logger.info(f" [{project_id}] 无新 Bug") + if not bugs: + logger.info(f" [{project_id}] 无待修复 Bug") continue - count = len(all_bugs) + count = len(bugs) total_found += count logger.info(f" [{project_id}] 发现 {count} 个待修复 Bug") @@ -120,51 +117,6 @@ class RepairScheduler: finally: task_manager.close() - @staticmethod - def _fetch_pending_fix_bugs( - task_manager: TaskManager, project_id: str - ) -> list: - """获取 PENDING_FIX 状态的 Bug(由 UI 触发)""" - try: - import httpx - - params = {"status": "PENDING_FIX", "project_id": project_id} - response = task_manager.client.get( - f"{task_manager.base_url}/api/v1/bugs", params=params - ) - response.raise_for_status() - data = response.json() - - from ..models import Bug, BugStatus - - bugs = [] - for item in data.get("items", []): - stack_trace = item.get("stack_trace") - if isinstance(stack_trace, str): - stack_trace = stack_trace.split("\n") - - bug = Bug( - id=item["id"], - project_id=item["project_id"], - environment=item.get("environment", "production"), - level=item.get("level", "ERROR"), - error={ - "type": item.get("error_type", "Unknown"), - "message": item.get("error_message", ""), - "file_path": item.get("file_path"), - "line_number": item.get("line_number"), - "stack_trace": stack_trace, - }, - context=item.get("context"), - status=BugStatus(item.get("status", "PENDING_FIX")), - retry_count=item.get("retry_count", 0), - ) - bugs.append(bug) - return bugs - except Exception as e: - logger.debug(f"获取 PENDING_FIX Bug 失败: {e}") - return [] - def _run_repair(self, project_id: str) -> int: """对指定项目执行修复,返回成功修复的数量""" if self._repairing: diff --git a/repair_agent/agent/task_manager.py b/repair_agent/agent/task_manager.py index 445f1ba..40eefd8 100644 --- a/repair_agent/agent/task_manager.py +++ b/repair_agent/agent/task_manager.py @@ -18,58 +18,60 @@ class TaskManager: def fetch_pending_bugs(self, project_id: Optional[str] = None) -> list[Bug]: """ - 获取待修复的 Bug 列表 - + 获取待修复的 Bug 列表(包括 NEW 和 PENDING_FIX 状态) + Args: project_id: 可选,筛选特定项目 - + Returns: - Bug 列表 + Bug 列表(已按 id 去重) """ - try: - params = {"status": "NEW"} - if project_id: - params["project_id"] = project_id - - response = self.client.get( - f"{self.base_url}/api/v1/bugs", - params=params - ) - response.raise_for_status() - - data = response.json() - bugs = [] - - for item in data.get("items", []): - # stack_trace 可能是列表或字符串 - stack_trace = item.get("stack_trace") - if isinstance(stack_trace, str): - stack_trace = stack_trace.split("\n") - - bug = Bug( - id=item["id"], - project_id=item["project_id"], - environment=item.get("environment", "production"), - level=item.get("level", "ERROR"), - error={ - "type": item.get("error_type", "Unknown"), - "message": item.get("error_message", ""), - "file_path": item.get("file_path"), - "line_number": item.get("line_number"), - "stack_trace": stack_trace, - }, - context=item.get("context"), - status=BugStatus(item.get("status", "NEW")), - retry_count=item.get("retry_count", 0), + all_bugs: dict[int, Bug] = {} + + for status in ("NEW", "PENDING_FIX"): + try: + params: dict[str, str] = {"status": status} + if project_id: + params["project_id"] = project_id + + response = self.client.get( + f"{self.base_url}/api/v1/bugs", + params=params, ) - bugs.append(bug) - - logger.info(f"获取到 {len(bugs)} 个待修复 Bug") - return bugs - - except httpx.HTTPError as e: - logger.error(f"获取 Bug 列表失败: {e}") - return [] + response.raise_for_status() + + data = response.json() + for item in data.get("items", []): + bug_id = item["id"] + if bug_id in all_bugs: + continue + + stack_trace = item.get("stack_trace") + if isinstance(stack_trace, str): + stack_trace = stack_trace.split("\n") + + all_bugs[bug_id] = Bug( + id=bug_id, + project_id=item["project_id"], + environment=item.get("environment", "production"), + level=item.get("level", "ERROR"), + error={ + "type": item.get("error_type", "Unknown"), + "message": item.get("error_message", ""), + "file_path": item.get("file_path"), + "line_number": item.get("line_number"), + "stack_trace": stack_trace, + }, + context=item.get("context"), + status=BugStatus(item.get("status", "NEW")), + retry_count=item.get("retry_count", 0), + ) + except httpx.HTTPError as e: + logger.error(f"获取 {status} 状态 Bug 列表失败: {e}") + + bugs = list(all_bugs.values()) + logger.info(f"获取到 {len(bugs)} 个待修复 Bug(NEW + PENDING_FIX)") + return bugs def update_status(self, bug_id: int, status: BugStatus, message: str = "") -> bool: """