fix(mention): address PR #45 review feedback (M1-M3, S1-S3)
M1: Remove '帮忙' from help_keywords to fix keyword priority bug M3: Add unit tests for mention_utils (§25.7) S1: Merge two 'if action == opened' blocks in _handle_issues S2: Extract _send_review_mentions helper to deduplicate @mention code S3: Remove redundant 'import re' inside conditional block
This commit is contained in:
@@ -110,8 +110,8 @@ def infer_intent(body: str) -> str:
|
||||
if any(kw in body for kw in assign_keywords):
|
||||
return "assign"
|
||||
|
||||
# 求助关键词
|
||||
help_keywords = ["怎么", "如何", "?", "?", "什么", "哪个", "能否", "帮忙"]
|
||||
# 求助关键词(注意:"帮忙"已由 assign_keywords 的"帮忙做"覆盖,"请帮忙"由 collab_keywords 覆盖)
|
||||
help_keywords = ["怎么", "如何", "?", "?", "什么", "哪个", "能否"]
|
||||
if any(kw in body for kw in help_keywords):
|
||||
return "help"
|
||||
|
||||
|
||||
+41
-46
@@ -400,6 +400,32 @@ async def _handle_pr_opened(payload: Dict[str, Any]) -> None:
|
||||
)
|
||||
|
||||
|
||||
async def _send_review_mentions(
|
||||
review_body: str,
|
||||
reviewer: str,
|
||||
pr_author: str,
|
||||
pr: dict,
|
||||
repo: str,
|
||||
pr_number: int,
|
||||
) -> None:
|
||||
"""提取并发送 Review body 中的 @mention 通知(COMMENTED / 非 COMMENTED 通用)。"""
|
||||
mentions = extract_mentions(review_body, reviewer)
|
||||
if mentions:
|
||||
auto_targets = [pr_author]
|
||||
await _send_mention_mails(
|
||||
mentions=mentions,
|
||||
auto_targets=auto_targets,
|
||||
source_type="Review",
|
||||
mention_type="Review @mention",
|
||||
source_url=pr.get("html_url", ""),
|
||||
commenter=reviewer,
|
||||
content=review_body,
|
||||
repo=repo,
|
||||
issue_number=pr_number,
|
||||
is_pr=True,
|
||||
)
|
||||
|
||||
|
||||
async def _handle_pull_request_review(payload: Dict[str, Any]) -> None:
|
||||
"""处理 pull_request_review 事件:非 COMMENTED → 通知 PR 作者。
|
||||
|
||||
@@ -463,22 +489,7 @@ async def _handle_pull_request_review(payload: Dict[str, Any]) -> None:
|
||||
_send_mail(pr_author, title, text)
|
||||
|
||||
# S5: Review body @mention 通知(COMMENTED 路径)
|
||||
mentions = extract_mentions(review_body, reviewer)
|
||||
if mentions:
|
||||
# 自动流转已通知 PR 作者(review_comment)
|
||||
auto_targets = [pr_author]
|
||||
await _send_mention_mails(
|
||||
mentions=mentions,
|
||||
auto_targets=auto_targets,
|
||||
source_type="Review",
|
||||
mention_type="Review @mention",
|
||||
source_url=pr.get("html_url", ""),
|
||||
commenter=reviewer,
|
||||
content=review_body,
|
||||
repo=repo,
|
||||
issue_number=pr_number,
|
||||
is_pr=True,
|
||||
)
|
||||
await _send_review_mentions(review_body, reviewer, pr_author, pr, repo, pr_number)
|
||||
|
||||
return
|
||||
|
||||
@@ -500,22 +511,7 @@ async def _handle_pull_request_review(payload: Dict[str, Any]) -> None:
|
||||
_send_mail(pr_author, title, text)
|
||||
|
||||
# S5: Review body @mention 通知(非 COMMENTED 路径)
|
||||
mentions = extract_mentions(review_body, reviewer)
|
||||
if mentions:
|
||||
# 自动流转已通知 PR 作者(review_result)
|
||||
auto_targets = [pr_author]
|
||||
await _send_mention_mails(
|
||||
mentions=mentions,
|
||||
auto_targets=auto_targets,
|
||||
source_type="Review",
|
||||
mention_type="Review @mention",
|
||||
source_url=pr.get("html_url", ""),
|
||||
commenter=reviewer,
|
||||
content=review_body,
|
||||
repo=repo,
|
||||
issue_number=pr_number,
|
||||
is_pr=True,
|
||||
)
|
||||
await _send_review_mentions(review_body, reviewer, pr_author, pr, repo, pr_number)
|
||||
|
||||
|
||||
async def _fetch_latest_reviewer(repo: str, pr_number: int) -> str:
|
||||
@@ -700,7 +696,6 @@ async def _handle_pr_closed(payload: Dict[str, Any]) -> None:
|
||||
self_restart = False
|
||||
if pm2_name and os.environ.get("PM2_HOME") and "pm2 restart" in cmd:
|
||||
# 检查命令是否包含当前进程名
|
||||
import re
|
||||
if re.search(rf'pm2\s+restart\s+{re.escape(pm2_name)}', cmd):
|
||||
self_restart = True
|
||||
|
||||
@@ -792,22 +787,22 @@ async def _handle_issues(payload: Dict[str, Any]) -> None:
|
||||
title = f"Issue 指派: {issue_title} ({repo}#{issue_number})"
|
||||
_send_mail(assignee, title, text)
|
||||
|
||||
elif action == "opened" and "部署失败" in issue_title:
|
||||
# 从 Issue body 提取 commit hash(Gitea deploy workflow 格式)
|
||||
sha_match = re.search(r'[0-9a-f]{40}', issue.get("body", ""))
|
||||
commit_sha = sha_match.group(0) if sha_match else "(未知)"
|
||||
elif action == "opened":
|
||||
if "部署失败" in issue_title:
|
||||
# 从 Issue body 提取 commit hash(Gitea deploy workflow 格式)
|
||||
sha_match = re.search(r'[0-9a-f]{40}', issue.get("body", ""))
|
||||
commit_sha = sha_match.group(0) if sha_match else "(未知)"
|
||||
|
||||
text = render_template("deploy_failure", {
|
||||
"repo": repo,
|
||||
"commit_sha": commit_sha or "(未知)",
|
||||
})
|
||||
text = render_template("deploy_failure", {
|
||||
"repo": repo,
|
||||
"commit_sha": commit_sha or "(未知)",
|
||||
})
|
||||
|
||||
title = f"部署失败: {repo}"
|
||||
for agent_id in ("jiangwei-infra", "pangtong-fujunshi"):
|
||||
_send_mail(agent_id, title, text)
|
||||
title = f"部署失败: {repo}"
|
||||
for agent_id in ("jiangwei-infra", "pangtong-fujunshi"):
|
||||
_send_mail(agent_id, title, text)
|
||||
|
||||
# S1: Issue body @mention(opened 时检查)
|
||||
if action == "opened":
|
||||
# Issue body @mention(opened 时检查)
|
||||
issue_body = issue.get("body", "") or ""
|
||||
sender = payload.get("sender", {}).get("login", "")
|
||||
mentions = extract_mentions(issue_body, sender)
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
"""mention_utils 单元测试 — §25.7 覆盖。"""
|
||||
|
||||
import pytest
|
||||
|
||||
from src.api.mention_utils import (
|
||||
extract_mentions,
|
||||
should_suppress_mention,
|
||||
infer_intent,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# extract_mentions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestExtractMentions:
|
||||
"""测试 @mention 提取逻辑。"""
|
||||
|
||||
def test_exact_match(self):
|
||||
"""@zhangfei-dev 精确匹配。"""
|
||||
assert extract_mentions("@zhangfei-dev 请看一下", "someone") == ["zhangfei-dev"]
|
||||
|
||||
def test_chinese_alias(self):
|
||||
"""@张飞 中文别名匹配。"""
|
||||
assert extract_mentions("@张飞 帮忙看看", "someone") == ["zhangfei-dev"]
|
||||
|
||||
def test_english_short_name(self):
|
||||
"""@zhangfei 英文短名匹配。"""
|
||||
assert extract_mentions("@zhangfei 快来", "someone") == ["zhangfei-dev"]
|
||||
|
||||
def test_prefix_unique(self):
|
||||
"""@zhangf 前缀唯一匹配。"""
|
||||
assert extract_mentions("@zhangf 来一下", "someone") == ["zhangfei-dev"]
|
||||
|
||||
def test_prefix_ambiguous_no_match(self):
|
||||
"""@z 前缀模糊,多个候选,不匹配。"""
|
||||
assert extract_mentions("@z 看看", "someone") == []
|
||||
|
||||
def test_dedup_same_person(self):
|
||||
"""@张飞 @zhangfei-dev 同时出现去重。"""
|
||||
result = extract_mentions("@张飞 @zhangfei-dev 来一下", "someone")
|
||||
assert result == ["zhangfei-dev"]
|
||||
|
||||
def test_exclude_self(self):
|
||||
"""@zhangfei-dev 排除自己(sender=zhangfei-dev)。"""
|
||||
assert extract_mentions("@zhangfei-dev 自己说", "zhangfei-dev") == []
|
||||
|
||||
def test_unknown_person(self):
|
||||
"""@unknown 不匹配任何 Agent。"""
|
||||
assert extract_mentions("@unknown 你好", "someone") == []
|
||||
|
||||
def test_multiple_mentions(self):
|
||||
"""多个 @mention 返回多个 Agent。"""
|
||||
result = set(extract_mentions("@张飞 @关羽 来讨论", "someone"))
|
||||
assert result == {"zhangfei-dev", "guanyu-dev"}
|
||||
|
||||
def test_mention_with_hyphen_in_middle(self):
|
||||
"""@mention 后面紧跟标点也能识别。"""
|
||||
result = extract_mentions("@赵云,请看下", "someone")
|
||||
assert result == ["zhaoyun-data"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# should_suppress_mention
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestShouldSuppressMention:
|
||||
"""测试 @mention 通知抑制逻辑。"""
|
||||
|
||||
def test_suppress_when_in_list(self):
|
||||
"""被提及者在自动通知列表中 → 抑制。"""
|
||||
assert should_suppress_mention("zhangfei-dev", ["zhangfei-dev", "guanyu-dev"]) is True
|
||||
|
||||
def test_not_suppress_when_not_in_list(self):
|
||||
"""被提及者不在自动通知列表中 → 不抑制。"""
|
||||
assert should_suppress_mention("zhangfei-dev", ["guanyu-dev"]) is False
|
||||
|
||||
def test_suppress_empty_list(self):
|
||||
"""自动通知列表为空 → 不抑制。"""
|
||||
assert should_suppress_mention("zhangfei-dev", []) is False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# infer_intent
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestInferIntent:
|
||||
"""测试意图推断逻辑。
|
||||
|
||||
优先级:assign → collaborate → help → notify(默认)
|
||||
"""
|
||||
|
||||
def test_help_question_mark(self):
|
||||
"""疑问句 → help。"""
|
||||
assert infer_intent("@赵云 数据格式是什么?") == "help"
|
||||
|
||||
def test_notify_plain_mention(self):
|
||||
"""纯通知(无关键词) → notify。"""
|
||||
assert infer_intent("@关羽 这个 PR 涉及风控变更") == "notify"
|
||||
|
||||
def test_collaborate_please_help(self):
|
||||
"""'请帮忙' → collaborate(NOT help!)。"""
|
||||
assert infer_intent("@庞统 请帮忙澄清需求") == "collaborate"
|
||||
|
||||
def test_assign_keywords(self):
|
||||
"""'交给你' → assign。"""
|
||||
assert infer_intent("@张飞 前端部分交给你") == "assign"
|
||||
|
||||
def test_help_how_to(self):
|
||||
"""'如何' → help。"""
|
||||
assert infer_intent("@姜维 如何部署这个服务") == "help"
|
||||
|
||||
def test_collaborate_please_review(self):
|
||||
"""'请review' → collaborate。"""
|
||||
assert infer_intent("@司马懿 请review 这个方案") == "collaborate"
|
||||
|
||||
def test_notify_default(self):
|
||||
"""无任何关键词 → notify。"""
|
||||
assert infer_intent("@赵云 已更新数据") == "notify"
|
||||
|
||||
def test_assign_takes_priority_over_help(self):
|
||||
"""assign 关键词优先于 help 关键词。"""
|
||||
# "交给" in body → assign, even though "?" also present
|
||||
assert infer_intent("@张飞 这个模块交给你,有问题?") == "assign"
|
||||
|
||||
def test_collaborate_takes_priority_over_help(self):
|
||||
"""collaborate 关键词优先于 help 关键词。"""
|
||||
# "请帮忙" in body → collaborate, even though "?" absent
|
||||
assert infer_intent("@赵云 请帮忙看看数据") == "collaborate"
|
||||
Reference in New Issue
Block a user