From 629c33917fced3c4334bdd5c300779b4584e5622 Mon Sep 17 00:00:00 2001 From: congkechen Date: Thu, 16 Jul 2026 17:51:07 +0800 Subject: [PATCH 1/2] Code Review --- .github/code_review/prompts/findings.md | 64 +++++++ .github/code_review/prompts/review.md | 78 ++++++++ .github/code_review/scripts/evaluate_gate.py | 24 +++ .../scripts/post_inline_comments.py | 170 ++++++++++++++++++ .../scripts/post_review_comment.py | 75 ++++++++ 5 files changed, 411 insertions(+) create mode 100644 .github/code_review/prompts/findings.md create mode 100644 .github/code_review/prompts/review.md create mode 100644 .github/code_review/scripts/evaluate_gate.py create mode 100644 .github/code_review/scripts/post_inline_comments.py create mode 100644 .github/code_review/scripts/post_review_comment.py diff --git a/.github/code_review/prompts/findings.md b/.github/code_review/prompts/findings.md new file mode 100644 index 00000000..15aa0c90 --- /dev/null +++ b/.github/code_review/prompts/findings.md @@ -0,0 +1,64 @@ +你现在的任务不是重新做代码审查,而是将已经生成的 `review.md` 提取为结构化结果,供后续脚本用于行内评论、阻断判断和其他自动化处理。 + +输入文件: +1. 仓库根目录下的 `review.md`:这是主依据,包含第一阶段已经确认的问题。 +2. 仓库根目录下的 `pr.diff`:仅用于辅助定位文件路径和行号,必要时可用于补全位置;不要基于 `pr.diff` 新增问题。 + +任务要求: +1. 只提取 `review.md` 中已经明确列出的问题,不要新增、扩展或重写任何新问题。 +2. 不要重新判断代码是否有问题;第一阶段 review 的结论已经成立,你的任务只是标准化提取。 +3. 如果 `review.md` 中某个问题缺少明确的文件路径或行号,不要编造;但不要跳过该问题,仍应保留该 finding,并将 `inline_candidate` 设为 `false`。在这种情况下,`path`、`start_line`、`end_line` 可留空字符串、0 或与定位失败一致的保守值,但不能让该问题从结果中消失。 +4. 允许参考 `pr.diff` 来确认问题对应的目标文件路径、起始行和结束行,但不要因此新增新的发现。 +5. 输出必须是严格合法的 JSON,且只能输出 JSON,不要输出 Markdown、解释文字、代码块、前后缀或额外说明。 +6. 输出必须直接以 `{` 开始、以 `}` 结束;不要使用 ```json、``` 或任何其他 Markdown 代码块围栏包裹 JSON。 + +严重级别映射: +- `Critical` -> `critical` +- `Warning` -> `warning` +- `Suggestion` -> `suggestion` + +输出 JSON 格式: +```json +{ + "findings": [ + { + "severity": "critical", + "path": "relative/path/to/file.py", + "start_line": 12, + "end_line": 12, + "title": "问题标题", + "body": "1-3 句简洁说明,适合直接用于 GitHub 行内评论。", + "inline_candidate": true + } + ] +} +``` + +字段要求: +1. `severity`:必须是 `critical`、`warning`、`suggestion` 之一。 +2. `path`:仓库相对路径,例如 `tests/ai_test.py`;如果确实无法稳定定位,可为空字符串,但该 finding 仍需保留。 +3. `start_line`:问题起始行号,必须优先使用变更后文件的目标行号;如果确实无法稳定定位,可为 `0`,但该 finding 仍需保留。 +4. `end_line`: + - 单行问题时,填写与 `start_line` 相同的值; + - 范围问题时,填写结束行号。 +5. `title`:简短问题标题,适合作为行内评论标题,不要过长。 +6. `body`:1-3 句简洁说明,说明风险和建议修复方向,适合直接显示在代码旁边。 +7. `inline_candidate`: + - `true`:适合做行内评论,问题能明确归因到一个文件的一行或一小段范围,且适合直接挂在代码旁解释。 + - `false`:更适合放在总评论中,例如跨文件、跨逻辑链路、整体性结论、位置不稳定或无法明确锚定到 diff 行的问题。 + +提取规则: +1. 如果 `review.md` 中同一个问题覆盖多个独立位置,只有在这些位置都适合单独挂行内评论时,才拆成多条 findings;否则保持为一条并将 `inline_candidate` 设为 `false`。 +2. 如果一个问题已经在第一阶段被合并表述为一个综合问题(例如“命令注入 + 路径穿越”),不要在这里再次拆分成多个新问题,除非 `review.md` 本身已经明确拆开。 +3. `body` 不要照抄整段 review 原文;应提炼成适合行内评论展示的短说明。 +4. 不允许因为定位失败、信息缺失或不适合行内评论而省略第一阶段已经明确列出的问题;这类问题必须保留在 `findings` 中,并通过 `inline_candidate=false` 与保守字段值表达。 +5. 如果 `review.md` 中没有任何问题,输出: +```json +{"findings":[]} +``` + +校验要求: +1. 输出必须能被标准 JSON 解析器直接解析。 +2. 所有字符串必须使用双引号。 +3. 不要在 JSON 外再输出任何额外字符。 +4. 不要输出 ```json 或 ```,也不要输出任何注释。 diff --git a/.github/code_review/prompts/review.md b/.github/code_review/prompts/review.md new file mode 100644 index 00000000..67eb19f4 --- /dev/null +++ b/.github/code_review/prompts/review.md @@ -0,0 +1,78 @@ +你是一个资深代码审查助手。请审查当前 PR 的代码变更,并给出稳定、可复用、可执行的 review 结论。 + +审查范围: +1. 以仓库根目录下的 `pr.diff` 作为审查主范围,只反馈落在 diff 变更中的问题。 +2. 允许结合仓库中的相关上下文辅助判断 diff 是否存在问题,例如被调用方、配置、类型定义、测试、公共函数和上下游调用关系;但不要脱离 diff 单独审查未修改代码。 +3. 不要修改任何文件,不要提交代码。 +4. 只反馈可以从 diff 及其相关仓库上下文中定位和验证的问题;不要基于猜测扩展到未修改代码。 +5. 每个问题必须标注文件路径和行号。行号优先使用 diff 中新增代码对应的目标文件行号;如果只能定位到代码块,请说明“附近行”。 + +审查重点: +1. 正确性:逻辑错误、状态流转错误、条件判断错误、返回值或异常处理错误。 +2. 安全性:凭证泄露、命令注入、路径穿越、不可信输入未校验、权限绕过、敏感信息输出。 +3. 稳定性:边界条件缺失、空值处理、并发或异步时序问题、资源未释放、超时和重试缺失。 +4. 兼容性:公开 API、配置项、持久化数据、CI/workflow 行为的破坏性变更。 +5. 测试有效性:高风险逻辑缺少必要测试,或测试没有覆盖实际风险路径。 +6. 可维护性:仅在影响理解、扩展或长期维护时提出,不输出泛泛的代码风格建议。 + +质量等级定义: +- 🚨 Critical:必须修复的问题。包括安全漏洞、明确的逻辑错误、会导致核心功能失败、数据错误、权限问题、CI 阻塞或线上风险的问题。 +- ⚠️ Warning:建议修复的问题。包括性能隐患、边界条件缺失、异常路径不完整、兼容性风险、测试覆盖不足等可能导致问题但不一定立即失败的情况。 +- 💡 Suggestion:可选优化。包括代码可读性、结构简化、维护性提升、轻微重复等不影响当前正确性的改进建议。 + +输出要求: +1. 使用 Markdown 输出,保持简洁,避免长篇解释。 +2. 先列问题,再给总结。 +3. 按 Critical、Warning、Suggestion 的顺序输出;同级别内按风险从高到低排序。 +4. 合并同类问题:同一根因、同一文件相邻代码、同一修复方式的问题应合并为一条,不要重复展开,也不要用不同表述重复描述同一问题。 +5. 优先输出高价值问题。Critical 和 Warning 不限数量但要合并;Suggestion 最多 2 条,且只有在确实有维护价值时输出。 +6. 每条问题控制在 2-3 句话内:说明问题、影响和修复方向即可,不要分别展开大段“问题/影响/建议”。 +7. 问题位置必须使用反引号包裹的 `文件路径:行号` 或 `文件路径:起始行-结束行` 格式,便于评论自动生成 GitHub 跳转链接。 +8. 如果没有发现明确问题,请直接说明“未发现明显阻塞问题”,不要为了完整性补充低价值建议。 +9. 如果同一段代码同时涉及多个相关风险(如命令注入、路径穿越),优先合并成一条,除非修复方式或影响范围明显不同。 +10. 对测试代码、示例代码、演示代码也按真实执行风险评估;如果这些代码会进入 CI、测试运行路径或被其他开发者复用,不要因为“仅用于示例/测试”而降低问题等级。 +11. 仅当问题离开具体代码片段不易理解时,才附带 3-8 行最小必要代码片段;前后可用 `...` 省略,不要默认给每个问题都附代码块,也不要贴大段代码。 +12. 结论要尽量确定:只有在确实无法从 diff 验证时,才使用“可能”“疑似”等措辞。 + +输出格式: + +## 发现的问题 + +如果存在问题,请按下面格式逐条输出。每条尽量一行位置 + 一段简短说明: + +### 🚨 Critical + +- `文件路径:行号`:问题标题 + - 简要说明具体风险和建议修复方式。 + - 如有必要,可附最小必要代码片段: + ```python + ... + 问题代码 + ... + ``` + +### ⚠️ Warning + +- `文件路径:行号`:问题标题 + - 简要说明具体风险和建议修复方式。 + - 如有必要,可附最小必要代码片段: + ```python + ... + 问题代码 + ... + ``` + +### 💡 Suggestion + +- `文件路径:行号`:问题标题 + - 简要说明维护性影响和可选优化方式。 + +如果某个等级没有问题,可以省略该等级。 + +## 总结 + +用 1-2 句话说明整体风险,以及是否存在必须修复的问题。 + +## 测试建议 + +如果需要补充测试,用 1-2 条说明建议覆盖的场景;如果不需要,请说明“暂无额外测试建议”。 diff --git a/.github/code_review/scripts/evaluate_gate.py b/.github/code_review/scripts/evaluate_gate.py new file mode 100644 index 00000000..c89ea29e --- /dev/null +++ b/.github/code_review/scripts/evaluate_gate.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 + +import json +import os +import sys + + +def main(): + findings_path = os.environ.get("FINDINGS_PATH", "findings.json") + with open(findings_path, "r", encoding="utf-8") as f: + data = json.load(f) + + findings = data.get("findings", []) + for finding in findings: + if finding.get("severity") == "critical": + print("Critical issues found in AI Code Review, blocking pipeline") + return 1 + + print("No critical findings, pipeline can continue") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/code_review/scripts/post_inline_comments.py b/.github/code_review/scripts/post_inline_comments.py new file mode 100644 index 00000000..5a6d0324 --- /dev/null +++ b/.github/code_review/scripts/post_inline_comments.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 + +import json +import os +import re +import sys +import urllib.error +import urllib.request + + +def read_findings(path): + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + return data.get("findings", []) + + +def build_position_map(diff_path): + with open(diff_path, "r", encoding="utf-8") as f: + lines = f.readlines() + + position_map = {} + current_path = None + new_line_no = None + position = 0 + in_hunk = False + + for raw in lines: + line = raw.rstrip("\n") + + if line.startswith("diff --git "): + current_path = None + new_line_no = None + position = 0 + in_hunk = False + continue + + if line.startswith("+++ b/"): + current_path = line[6:] + if current_path not in position_map: + position_map[current_path] = {} + continue + + if line.startswith("@@ "): + match = re.match(r"@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@", line) + if match: + new_line_no = int(match.group(1)) + in_hunk = True + continue + + if not in_hunk or current_path is None: + continue + + if not line: + continue + + prefix = line[0] + if prefix not in (" ", "+", "-"): + continue + + position += 1 + + if prefix == " ": + position_map[current_path][new_line_no] = position + new_line_no += 1 + elif prefix == "+": + position_map[current_path][new_line_no] = position + new_line_no += 1 + elif prefix == "-": + continue + + return position_map + + +def build_payload(commit_id, path, position, title, body): + comment_body = "**%s**" % title + if body: + comment_body += "\n\n%s" % body + return { + "body": comment_body, + "commit_id": commit_id, + "path": path, + "position": position, + } + + +def post_inline_comment(api_url, token, payload): + data = json.dumps(payload).encode("utf-8") + req = urllib.request.Request( + api_url, + data=data, + method="POST", + headers={ + "Authorization": "Bearer %s" % token, + "Accept": "application/vnd.github+json", + "Content-Type": "application/json", + }, + ) + try: + with urllib.request.urlopen(req) as resp: + return True, "status=%s" % resp.status + except urllib.error.HTTPError as e: + try: + err = e.read().decode("utf-8", errors="ignore") + except Exception: + err = "" + return False, "status=%s body=%s" % (e.code, err) + + +def main(): + findings_path = os.environ.get("FINDINGS_PATH", "findings.json") + diff_path = os.environ.get("PR_DIFF_PATH", "pr.diff") + api_url = os.environ["INLINE_COMMENT_API_URL"] + token = os.environ["GITHUB_TOKEN"] + commit_id = os.environ["GITHUB_HEAD_SHA"] + + findings = read_findings(findings_path) + if not findings: + print("no findings found, skip inline comments") + return 0 + + position_map = build_position_map(diff_path) + created = 0 + + for finding in findings: + if finding.get("severity") != "critical": + continue + if not finding.get("inline_candidate"): + continue + + path = finding.get("path") + start_line = finding.get("start_line") + end_line = finding.get("end_line") + title = finding.get("title", "").strip() + body = finding.get("body", "").strip() + + if not path or not start_line or not title: + print("skip finding without required fields: %s" % json.dumps(finding, ensure_ascii=False)) + continue + + start_line = int(start_line) + end_line = int(end_line) if end_line else None + target_line = start_line + + file_positions = position_map.get(path, {}) + position = file_positions.get(target_line) + if position is None and end_line and end_line > start_line: + position = file_positions.get(end_line) + if position is not None: + target_line = end_line + + target = "%s:%s-%s" % (path, start_line, end_line) if end_line and end_line > start_line else "%s:%s" % (path, start_line) + + if position is None: + print("skip finding without diff position: %s" % target) + continue + + payload = build_payload(commit_id, path, position, title, body) + ok, msg = post_inline_comment(api_url, token, payload) + if ok: + created += 1 + print("inline comment created: %s position=%s %s" % (target, position, msg)) + else: + print("inline comment failed: %s position=%s %s" % (target, position, msg)) + + print("inline comments created: %s" % created) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/code_review/scripts/post_review_comment.py b/.github/code_review/scripts/post_review_comment.py new file mode 100644 index 00000000..89bcfa03 --- /dev/null +++ b/.github/code_review/scripts/post_review_comment.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +import json +import os +import re +import sys +import urllib.error +import urllib.request + + +def load_review(path): + with open(path, "r", encoding="utf-8") as f: + return f.read().strip() + + +def add_github_links(body, repo_url, head_sha): + pattern = re.compile(r"`((?:\.?[\w.-]+/)*[\w.-]+\.[\w.+-]+):(\d+)(?:-(\d+))?`") + + def repl(match): + file_path = match.group(1) + start = match.group(2) + end = match.group(3) + label = "%s:%s%s" % (file_path, start, ("-%s" % end) if end else "") + anchor = "#L%s%s" % (start, ("-L%s" % end) if end else "") + url = "%s/blob/%s/%s%s" % (repo_url, head_sha, file_path, anchor) + return "[`%s`](%s)" % (label, url) + + return pattern.sub(repl, body) + + +def post_comment(api_url, token, body): + payload = json.dumps({"body": body}).encode("utf-8") + req = urllib.request.Request( + api_url, + data=payload, + method="POST", + headers={ + "Authorization": "Bearer %s" % token, + "Accept": "application/vnd.github+json", + "Content-Type": "application/json", + }, + ) + try: + with urllib.request.urlopen(req) as resp: + return resp.status, resp.read().decode("utf-8", errors="ignore") + except urllib.error.HTTPError as e: + return e.code, e.read().decode("utf-8", errors="ignore") + + +def main(): + review_path = os.environ.get("REVIEW_PATH", "review.md") + api_url = os.environ["COMMENT_API_URL"] + repo_url = os.environ["GITHUB_WEB_REPO_URL"].rstrip("/") + head_sha = os.environ["GITHUB_HEAD_SHA"] + token = os.environ["GITHUB_TOKEN"] + + body = load_review(review_path) + if not body: + body = "AI Code Review 未生成有效结果,请检查流水线日志。" + + body = add_github_links(body, repo_url, head_sha) + status, response = post_comment(api_url, token, "## AI Code Review\n\n%s" % body) + + print("comment http code: %s" % status) + if status < 200 or status >= 300: + print("failed to create github comment") + print(response) + return 1 + + print("comment created successfully") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From b31b3cc8f6296501354461735fe9855c713191b7 Mon Sep 17 00:00:00 2001 From: congkechen Date: Thu, 16 Jul 2026 17:54:22 +0800 Subject: [PATCH 2/2] remove ai review prompt file --- .github/ai-review-prompt.md | 78 ------------------------------------- 1 file changed, 78 deletions(-) delete mode 100644 .github/ai-review-prompt.md diff --git a/.github/ai-review-prompt.md b/.github/ai-review-prompt.md deleted file mode 100644 index d05928fd..00000000 --- a/.github/ai-review-prompt.md +++ /dev/null @@ -1,78 +0,0 @@ -你是一个资深代码审查助手。请审查当前 PR 的代码变更,并给出稳定、可复用、可执行的 review 结论。 - -审查范围: -1. 以仓库根目录下的 `pr.diff` 作为审查主范围,只反馈落在 diff 变更中的问题。 -2. 允许结合仓库中的相关上下文辅助判断 diff 是否存在问题,例如被调用方、配置、类型定义、测试、公共函数和上下游调用关系;但不要脱离 diff 单独审查未修改代码。 -3. 不要修改任何文件,不要提交代码。 -4. 只反馈可以从 diff 及其相关仓库上下文中定位和验证的问题;不要基于猜测扩展到未修改代码。 -5. 每个问题必须标注文件路径和行号。行号优先使用 diff 中新增代码对应的目标文件行号;如果只能定位到代码块,请说明“附近行”。 - -审查重点: -1. 正确性:逻辑错误、状态流转错误、条件判断错误、返回值或异常处理错误。 -2. 安全性:凭证泄露、命令注入、路径穿越、不可信输入未校验、权限绕过、敏感信息输出。 -3. 稳定性:边界条件缺失、空值处理、并发或异步时序问题、资源未释放、超时和重试缺失。 -4. 兼容性:公开 API、配置项、持久化数据、CI/workflow 行为的破坏性变更。 -5. 测试有效性:高风险逻辑缺少必要测试,或测试没有覆盖实际风险路径。 -6. 可维护性:仅在影响理解、扩展或长期维护时提出,不输出泛泛的代码风格建议。 - -质量等级定义: -- 🚨 Critical:必须修复的问题。包括安全漏洞、明确的逻辑错误、会导致核心功能失败、数据错误、权限问题、CI 阻塞或线上风险的问题。 -- ⚠️ Warning:建议修复的问题。包括性能隐患、边界条件缺失、异常路径不完整、兼容性风险、测试覆盖不足等可能导致问题但不一定立即失败的情况。 -- 💡 Suggestion:可选优化。包括代码可读性、结构简化、维护性提升、轻微重复等不影响当前正确性的改进建议。 - -输出要求: -1. 使用 Markdown 输出,保持简洁,避免长篇解释。 -2. 先列问题,再给总结。 -3. 按 Critical、Warning、Suggestion 的顺序输出;同级别内按风险从高到低排序。 -4. 合并同类问题:同一根因、同一文件相邻代码、同一修复方式的问题应合并为一条,不要重复展开,也不要用不同表述重复描述同一问题。 -5. 优先输出高价值问题。Critical 和 Warning 不限数量但要合并;Suggestion 最多 2 条,且只有在确实有维护价值时输出。 -6. 每条问题控制在 2-3 句话内:说明问题、影响和修复方向即可,不要分别展开大段“问题/影响/建议”。 -7. 问题位置必须使用反引号包裹的 `文件路径:行号` 或 `文件路径:起始行-结束行` 格式,便于评论自动生成 GitHub 跳转链接。 -8. 如果没有发现明确问题,请直接说明“未发现明显阻塞问题”,不要为了完整性补充低价值建议。 -9. 如果同一段代码同时涉及多个相关风险(如命令注入、路径穿越),优先合并成一条,除非修复方式或影响范围明显不同。 -10. 对测试代码、示例代码、演示代码也按真实执行风险评估;如果这些代码会进入 CI、测试运行路径或被其他开发者复用,不要因为“仅用于示例/测试”而降低问题等级。 -11. 仅当问题离开具体代码片段不易理解时,才附带 3-8 行最小必要代码片段;前后可用 `...` 省略,不要默认给每个问题都附代码块,也不要贴大段代码。 -12. 结论要尽量确定:只有在确实无法从 diff 验证时,才使用“可能”“疑似”等措辞。 - -输出格式: - -## 发现的问题 - -如果存在问题,请按下面格式逐条输出。每条尽量一行位置 + 一段简短说明: - -### 🚨 Critical - -- `文件路径:行号`:问题标题 - - 简要说明具体风险和建议修复方式。 - - 如有必要,可附最小必要代码片段: - ```python - ... - 问题代码 - ... - ``` - -### ⚠️ Warning - -- `文件路径:行号`:问题标题 - - 简要说明具体风险和建议修复方式。 - - 如有必要,可附最小必要代码片段: - ```python - ... - 问题代码 - ... - ``` - -### 💡 Suggestion - -- `文件路径:行号`:问题标题 - - 简要说明维护性影响和可选优化方式。 - -如果某个等级没有问题,可以省略该等级。 - -## 总结 - -用 1-2 句话说明整体风险,以及是否存在必须修复的问题。 - -## 测试建议 - -如果需要补充测试,用 1-2 条说明建议覆盖的场景;如果不需要,请说明“暂无额外测试建议”。 \ No newline at end of file