From 13f0e6b3dd4a65273b8cdd5e9e4476b04ac6db28 Mon Sep 17 00:00:00 2001 From: shenchucheng Date: Tue, 20 Aug 2024 20:22:12 +0800 Subject: [PATCH] add support for local file cr --- metagpt/ext/cr/actions/code_review.py | 29 +++++++++++++++-- metagpt/tools/libs/cr.py | 46 +++++++++++++++------------ 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/metagpt/ext/cr/actions/code_review.py b/metagpt/ext/cr/actions/code_review.py index e3e6e69f2..5f861c3e3 100644 --- a/metagpt/ext/cr/actions/code_review.py +++ b/metagpt/ext/cr/actions/code_review.py @@ -4,7 +4,9 @@ import json import re +from pathlib import Path +import aiofiles from unidiff import PatchSet from metagpt.actions.action import Action @@ -16,6 +18,7 @@ from metagpt.ext.cr.utils.cleaner import ( from metagpt.ext.cr.utils.schema import Point from metagpt.logs import logger from metagpt.utils.common import parse_json_code_block +from metagpt.utils.report import EditorReporter CODE_REVIEW_PROMPT_TEMPLATE = """ NOTICE @@ -193,16 +196,26 @@ class CodeReview(Action): patched_file_path = patched_file.path for c in comments_batch: c["commented_file"] = patched_file_path - comments += comments_batch + comments.extend(comments_batch) return comments - async def run(self, patch: PatchSet, points: list[Point]): + async def run(self, patch: PatchSet, points: list[Point], output_file: str): patch: PatchSet = rm_patch_useless_part(patch) patch: PatchSet = add_line_num_on_patch(patch) result = [] - comments = await self.cr_by_points(patch=patch, points=points) + async with EditorReporter(enable_llm_stream=True) as reporter: + log_cr_output_path = Path(output_file).with_suffix(".log") + await reporter.async_report( + {"src_path": str(log_cr_output_path), "filename": log_cr_output_path.name}, "meta" + ) + comments = await self.cr_by_points(patch=patch, points=points) + log_cr_output_path.parent.mkdir(exist_ok=True, parents=True) + async with aiofiles.open(log_cr_output_path, "w", encoding="utf-8") as f: + await f.write(json.dumps(comments, ensure_ascii=False, indent=2)) + await reporter.async_report(log_cr_output_path) + if len(comments) != 0: comments = self.format_comments(comments, points, patch) comments = await self.confirm_comments(patch=patch, comments=comments, points=points) @@ -210,4 +223,14 @@ class CodeReview(Action): if comment["code"]: if not (comment["code"].isspace()): result.append(comment) + + async with EditorReporter() as reporter: + src_path = output_file + cr_output_path = Path(output_file) + await reporter.async_report( + {"type": "CodeReview", "src_path": src_path, "filename": cr_output_path.name}, "meta" + ) + async with aiofiles.open(cr_output_path, "w", encoding="utf-8") as f: + await f.write(json.dumps(comments, ensure_ascii=False, indent=2)) + await reporter.async_report(cr_output_path) return result diff --git a/metagpt/tools/libs/cr.py b/metagpt/tools/libs/cr.py index 5fca23a66..cdc3f81ce 100644 --- a/metagpt/tools/libs/cr.py +++ b/metagpt/tools/libs/cr.py @@ -1,3 +1,4 @@ +import difflib import json from pathlib import Path from typing import Optional @@ -22,35 +23,34 @@ class CodeReview: async def review( self, patch_path: str, - cr_output_file: str, - cr_point_file: Optional[str] = None, + output_file: str, + point_file: Optional[str] = None, ) -> str: """Review a PR and save code review comments. + Notes: + If the user does not specify an output path, saved it using a relative path in the current working directory. + Args: - patch_path: The local path of the patch file or the url of the pull request. Example: "/data/xxx-pr-1.patch", "https://github.com/xx/XX/pull/1362" - cr_output_file: Output file path where code review comments will be saved. Example: "cr/xxx-pr-1.json" - cr_point_file: File path for specifying code review points. If not specified, this parameter is not passed.. + patch_path: The local path of the patch file or the URL of the pull request. + output_file: Output file path where code review comments will be saved. + point_file: File path for specifying code review points. If not specified, this parameter does not need to be passed. + + Examples: + + >>> cr = CodeReview() + >>> await cr.review(patch_path="https://github.com/geekan/MetaGPT/pull/136", output_file="cr/MetaGPT_136.json") + >>> await cr.review(patch_path="/data/uploads/dev-master.diff", output_file="cr/dev-master.json") + >>> await cr.review(patch_path="/data/uploads/main.py", output_file="cr/main.json") """ patch = await self._get_patch_content(patch_path) - cr_point_file = cr_point_file if cr_point_file else Path(metagpt.ext.cr.__file__).parent / "points.json" - async with aiofiles.open(cr_point_file, "rb") as f: + point_file = point_file if point_file else Path(metagpt.ext.cr.__file__).parent / "points.json" + async with aiofiles.open(point_file, "rb") as f: cr_point_content = await f.read() cr_points = [Point(**i) for i in json.loads(cr_point_content)] - async with EditorReporter(enable_llm_stream=True) as reporter: - src_path = cr_output_file - cr_output_path = Path(cr_output_file) - await reporter.async_report( - {"type": "CodeReview", "src_path": src_path, "filename": cr_output_path.name}, "meta" - ) - comments = await CodeReview_().run(patch, cr_points) - cr_output_path.parent.mkdir(exist_ok=True, parents=True) - async with aiofiles.open(cr_output_path, "w", encoding="utf-8") as f: - await f.write(json.dumps(comments, ensure_ascii=False)) - await reporter.async_report(cr_output_path) - - return f"The number of defects: {len(comments)} and the comments are stored in {cr_output_file}" + comments = await CodeReview_().run(patch, cr_points, output_file) + return f"The number of defects: {len(comments)} and the comments are stored in {output_file}" async def fix( self, @@ -88,6 +88,12 @@ class CodeReview: async with aiofiles.open(patch_path, encoding="utf-8") as f: patch_file_content = await f.read() await EditorReporter().async_report(patch_path) + if not patch_path.endswith((".diff", "patch")): + name = Path(patch_path).name + patch_file_content = "".join( + difflib.unified_diff([], patch_file_content.splitlines(keepends=True), "/dev/null", f"b/{name}"), + ) + patch_file_content = f"diff --git a/{name} b/{name}\n{patch_file_content}" patch: PatchSet = PatchSet(patch_file_content) return patch