diff --git a/.claude/agents/code-review-agent.md b/.claude/agents/code-review-agent.md index a7222e4..cb43a65 100644 --- a/.claude/agents/code-review-agent.md +++ b/.claude/agents/code-review-agent.md @@ -11,8 +11,9 @@ description: "Code Review Agent: 通过 Gitea API 获取 PR diff,分析代码 1. 读取提供的 PR diff 文件(路径在 prompt 中) 2. **(如提供)读取关联 Issue 文件,了解原始需求/缺陷描述** -3. **(如提供)读取全局影响报告,了解变更的影响范围** -4. 逐文件分析代码变更 +3. **(如提供)读取 code review 规范文档,根据 PR 变更类型查阅相关章节** +4. **(如提供)读取全局影响报告,了解变更的影响范围** +5. 逐文件分析代码变更 5. **使用搜索工具(Grep)追踪变更函数/类的调用方,验证接口兼容性** 6. **检查相关测试文件是否覆盖了变更逻辑** 7. **对比 Issue 验收条件,验证改动是否完整满足需求** diff --git a/scripts/gitea_api.py b/scripts/gitea_api.py index 166a945..28eeb66 100644 --- a/scripts/gitea_api.py +++ b/scripts/gitea_api.py @@ -178,3 +178,19 @@ def format_issues_markdown(issues): f"\n{iss['body']}\n" ) return "\n".join(parts) + + +_STANDARDS_URL = "/api/v1/repos/zeekrAI/knowledge-base/raw/docs/process/code-review.md?ref=main" + + +def fetch_standards_doc(gitea_url, api_token): + """Fetch the code-review standards document from Gitea via API. + + Returns the raw markdown text, or ``""`` on failure. + """ + try: + return _api_req(gitea_url, api_token, "GET", _STANDARDS_URL).decode( + "utf-8", errors="replace" + ) + except Exception: + return "" diff --git a/scripts/review_pr.py b/scripts/review_pr.py index 98e9290..4f0e989 100644 --- a/scripts/review_pr.py +++ b/scripts/review_pr.py @@ -225,7 +225,7 @@ def _strip_frontmatter(md_text): def build_prompt(meta, files, diff_path, agent_md, impact_report_path=None, - issue_context_path=None): + issue_context_path=None, standards_path=None): """Build the prompt string for Claude, embedding the agent definition.""" files_summary = format_files_summary(files) total_additions = sum(f["additions"] for f in files) @@ -264,6 +264,14 @@ def build_prompt(meta, files, diff_path, agent_md, impact_report_path=None, f"Review the diff in isolation.\n\n" ) + if standards_path: + prompt += ( + f"Code review standards document saved to: {standards_path}\n" + f"Read relevant sections of that document based on the files and\n" + f"changes in this PR. Use headings to find applicable rules — do NOT\n" + f"read the entire file unless necessary.\n\n" + ) + prompt += ( f"Analyze the diff thoroughly. Focus on: business correctness (vs linked " f"issues), security, correctness, error handling, performance, " @@ -415,6 +423,8 @@ def main(): help="Gitea API token with read:repository + write:repository") parser.add_argument("--repo-dir", default=None, help="Path to local clone of the target repo (enables global impact analysis)") + parser.add_argument("--review-standards", default=None, + help="Path to a code-review standards .md file to reference during review") args = parser.parse_args() claude_bin = _check_claude() @@ -504,12 +514,37 @@ def main(): f.write(issue_md) print(f" Issues saved to: {issue_context_path}") + # ── 4d. Fetch / copy review standards doc ─────────────────────────── + standards_path = None + if args.review_standards: + # Explicit local path provided + if not os.path.isfile(args.review_standards): + print(f"ERROR: --review-standards file not found: {args.review_standards}", file=sys.stderr) + sys.exit(1) + standards_path = os.path.join(diff_dir, f"{prefix}_standards.md") + shutil.copy2(args.review_standards, standards_path) + print(f" Standards doc copied to: {standards_path}") + else: + # Auto-fetch from knowledge-base repo via Gitea API + print("[review_pr] Fetching review standards from knowledge-base...") + standards_md = gitea_api.fetch_standards_doc(args.gitea_url, args.api_token) + if standards_md: + standards_path = os.path.join(diff_dir, f"{prefix}_standards.md") + with open(standards_path, "w", encoding="utf-8") as f: + f.write(standards_md) + print(f" Standards doc saved to: {standards_path}") + else: + print("[review_pr] Warning: failed to fetch review standards, continuing without") + standards_path = os.path.join(diff_dir, f"{prefix}_standards.md") + shutil.copy2(args.review_standards, standards_path) + print(f" Standards doc saved to: {standards_path}") + mon = None prompt_file = os.path.join(diff_dir, f"{prefix}_prompt.txt") try: # ── 5. Build prompt & run Claude ───────────────────────────────── prompt = build_prompt(meta, files, diff_path, agent_md, - impact_report_path, issue_context_path) + impact_report_path, issue_context_path, standards_path) print("[review_pr] Invoking Claude for analysis...") mon = SessionMonitor(project_root) @@ -541,6 +576,8 @@ def main(): os.unlink(impact_report_path) if issue_context_path and os.path.exists(issue_context_path): os.unlink(issue_context_path) + if standards_path and os.path.exists(standards_path): + os.unlink(standards_path) if __name__ == "__main__":