2626
2727DIFF_PATH = "/tmp/pr_diff.txt"
2828TITLE_PATH = "/tmp/pr_title.txt"
29+ RULES_PATH = "CLAUDE.md"
2930OUT_PATH = "/tmp/review_body.txt"
3031
3132BASE_URL = os .environ .get ("DEEPSEEK_BASE_URL" , "https://api.deepseek.com/v1" )
3738 你是这个仓库的资深 reviewer。目标:在合并前尽量拦住真正的 bug、设计缺陷、架构失误。
3839**不要只对着固定清单打勾**——清单覆盖不到新功能。要先理解再评审。
3940
40- ## Step 1 · 读项目规则(每次都重新读 ,规则会随项目迭代而变)
41+ ## Step 1 · 读项目规则(每次都重新注入 ,规则会随项目迭代而变)
4142
42- - 用 Read 读仓库根 `CLAUDE.md`;改动目录附近若有 `AGENTS.md` / 相关 `docs/` / ADR 也读。
43- - 把里面的硬约束当成本次 review 的**项目专属规则**——
44- CLAUDE.md 更新了,你的评审标准就自动跟着更新,**无需改这个 workflow**。
45- - 这是项目规则的唯一权威来源;下面 Step 4 的清单只是提示,以你读到的为准。
43+ - 用户消息中的“项目规则”就是仓库根 `CLAUDE.md` 的完整内容,把其中硬约束当作
44+ 本次 review 的项目专属标准。
45+ - 当前环境没有 Read / Grep / Glob 或其它工具。不要尝试调用工具,也不要输出工具调用协议;
46+ 只能基于项目规则、PR 标题和完整 diff 评审。
47+ - 规则更新后会由脚本自动重新注入,无需修改这个 workflow。
4648
4749## Step 2 · 重建意图 + 圈定影响面
4850
4951- 先一句话说清这个 PR 想做什么。
50- - 用 Read / Grep / Glob 看 diff **以外**的代码:改动的函数 / 接口 / 契约有哪些调用方?
51- 碰了哪些模块边界(Inalpha 是 Next.js → Mastra(TS) → Python services 三层)?
52- - 只有理解了"改动如何与系统其余部分交互",才谈得上架构评审 。
52+ - 只基于给定完整 diff 重建调用关系和模块边界。看不到 diff 外实现时,不要假装已经读取,
53+ 也不要输出工具请求;仅报告能由现有证据支持的问题。
54+ - 只有理解了改动如何与系统其余部分交互,才提出架构 finding 。
5355
5456## Step 3 · 通用工程评审(适用任何功能,新增功能也自动覆盖)
5557
@@ -95,16 +97,31 @@ def _fail(msg: str) -> None:
9597 sys .exit (0 )
9698
9799
98- def _call_deepseek (api_key : str , title : str , diff : str ) -> str :
99- """调用 DeepSeek;正常响应却无正文时重试一次,禁止发布空 review。"""
100+ def _is_valid_review (content : object ) -> bool :
101+ """只接受非空自然语言正文,拒绝模型泄漏的工具调用协议。"""
102+ if not isinstance (content , str ) or not content .strip ():
103+ return False
104+ protocol_markers = ("<||DSML||tool_calls>" , "<tool_call>" , '"tool_calls"' )
105+ return not any (marker in content for marker in protocol_markers )
106+
107+
108+ def _call_deepseek (api_key : str , title : str , diff : str , rules : str ) -> str :
109+ """调用 DeepSeek;空正文或工具协议泄漏时重试一次。"""
100110 payload = {
101111 "model" : MODEL ,
102112 "messages" : [
103113 {"role" : "system" , "content" : SYSTEM_PROMPT },
104- {"role" : "user" , "content" : f"## PR 标题\n { title } \n \n ## Diff\n { diff } " },
114+ {
115+ "role" : "user" ,
116+ "content" : (
117+ f"## 项目规则(CLAUDE.md)\n { rules } \n \n "
118+ f"## PR 标题\n { title } \n \n ## Diff\n { diff } "
119+ ),
120+ },
105121 ],
106122 "temperature" : 0.1 ,
107- "max_tokens" : 4096 ,
123+ # V4 Pro 会把 reasoning tokens 计入 completion;4096 会在输出正文前耗尽。
124+ "max_tokens" : 16384 ,
108125 }
109126 for attempt in range (MAX_ATTEMPTS ):
110127 req = urllib .request .Request (
@@ -119,16 +136,19 @@ def _call_deepseek(api_key: str, title: str, diff: str) -> str:
119136 with urllib .request .urlopen (req , timeout = TIMEOUT_S ) as resp :
120137 body = json .loads (resp .read ())
121138 content = body ["choices" ][0 ]["message" ].get ("content" )
122- if isinstance (content , str ) and content . strip ( ):
139+ if _is_valid_review (content ):
123140 return content .strip ()
124141 if attempt + 1 < MAX_ATTEMPTS :
125142 payload ["messages" ].append (
126143 {
127144 "role" : "user" ,
128- "content" : "上一轮没有返回 review 正文。请直接输出最终中文 review。" ,
145+ "content" : (
146+ "上一轮没有返回可发布的 review 正文。当前没有任何工具,"
147+ "禁止输出工具调用协议。请直接输出最终中文 review。"
148+ ),
129149 }
130150 )
131- raise ValueError ("DeepSeek 返回空 review 正文" )
151+ raise ValueError ("DeepSeek 未返回可发布的 review 正文" )
132152
133153
134154def main () -> None :
@@ -141,14 +161,16 @@ def main() -> None:
141161 diff = f .read ()
142162 with open (TITLE_PATH ) as f :
143163 title = f .read ().strip ()
164+ with open (RULES_PATH ) as f :
165+ rules = f .read ()
144166 except OSError as e :
145167 _fail (f"读输入失败:{ e } " )
146168
147169 if not diff .strip ():
148170 _fail ("diff 为空" )
149171
150172 try :
151- content = _call_deepseek (api_key , title , diff )
173+ content = _call_deepseek (api_key , title , diff , rules )
152174 except urllib .error .HTTPError as e :
153175 body = e .read ().decode ("utf-8" , "replace" )[:300 ]
154176 _fail (f"DeepSeek API HTTP { e .code } :{ body } " )
0 commit comments