@newstarzj/cann-review 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +168 -0
- package/LICENSE +21 -0
- package/README.md +186 -0
- package/SKILL.md +663 -0
- package/auto-review-final.sh +170 -0
- package/auto-review-post-comment.sh +197 -0
- package/auto-review-single-simple.sh +123 -0
- package/auto-review-single-v2.sh +183 -0
- package/auto-review-single.sh +172 -0
- package/auto-review.sh +170 -0
- package/check-mentions.py +145 -0
- package/check-mentions.sh +128 -0
- package/config/cron.example.json +9 -0
- package/config/gitcode.conf +10 -0
- package/config/gitcode.conf.example +10 -0
- package/config/repos.conf +13 -0
- package/config/repos.conf.example +15 -0
- package/cron.yaml +89 -0
- package/examples/auto_review_summary.md +210 -0
- package/examples/example_report.md +88 -0
- package/gitcode-api.sh +205 -0
- package/index.js +13 -0
- package/package.json +52 -0
- package/post-install.sh +41 -0
- package/prompt.md +212 -0
- package/simple-review.sh +56 -0
- package/skill.yaml +172 -0
- package/test-api.sh +86 -0
- package/test-auto-review.sh +26 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# 单次自动审查脚本 - 每次只审查一个 PR
|
|
3
|
+
# 避免上下文窗口超限
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
CONFIG_FILE="$SCRIPT_DIR/config/repos.conf"
|
|
7
|
+
STATE_FILE="$SCRIPT_DIR/.review-state.json"
|
|
8
|
+
|
|
9
|
+
# 加载 Token
|
|
10
|
+
if [ -f "$SCRIPT_DIR/config/gitcode.conf" ]; then
|
|
11
|
+
source "$SCRIPT_DIR/config/gitcode.conf"
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
if [ -z "$GITCODE_API_TOKEN" ]; then
|
|
15
|
+
echo "❌ 错误: 未配置 GitCode API Token"
|
|
16
|
+
echo "请运行: ./gitcode-api.sh setup"
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# 读取审查状态
|
|
21
|
+
load_state() {
|
|
22
|
+
if [ -f "$STATE_FILE" ]; then
|
|
23
|
+
cat "$STATE_FILE"
|
|
24
|
+
else
|
|
25
|
+
echo '{"reviewed": []}'
|
|
26
|
+
fi
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# 保存审查状态
|
|
30
|
+
save_state() {
|
|
31
|
+
echo "$1" > "$STATE_FILE"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# 检查 PR 是否已审查
|
|
35
|
+
is_reviewed() {
|
|
36
|
+
local repo=$1
|
|
37
|
+
local pr_number=$2
|
|
38
|
+
local state=$(load_state)
|
|
39
|
+
|
|
40
|
+
if echo "$state" | grep -q "\"$repo#$pr_number\""; then
|
|
41
|
+
return 0
|
|
42
|
+
else
|
|
43
|
+
return 1
|
|
44
|
+
fi
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# 标记为已审查
|
|
48
|
+
mark_reviewed() {
|
|
49
|
+
local repo=$1
|
|
50
|
+
local pr_number=$2
|
|
51
|
+
|
|
52
|
+
python3 << EOF
|
|
53
|
+
import json
|
|
54
|
+
from datetime import datetime
|
|
55
|
+
|
|
56
|
+
state = json.loads('''$(load_state)''')
|
|
57
|
+
key = "$repo#$pr_number"
|
|
58
|
+
|
|
59
|
+
if key not in state.get("reviewed", []):
|
|
60
|
+
if "reviewed" not in state:
|
|
61
|
+
state["reviewed"] = []
|
|
62
|
+
state["reviewed"].append(key)
|
|
63
|
+
state["last_review"] = {
|
|
64
|
+
"repo": "$repo",
|
|
65
|
+
"pr": $pr_number,
|
|
66
|
+
"time": datetime.now().isoformat()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
with open('$STATE_FILE', 'w') as f:
|
|
70
|
+
json.dump(state, f, indent=2)
|
|
71
|
+
EOF
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# 查找下一个需要审查的 PR
|
|
75
|
+
find_next_pr() {
|
|
76
|
+
while IFS= read -r repo; do
|
|
77
|
+
# 跳过注释和空行
|
|
78
|
+
[[ "$repo" =~ ^#.*$ ]] && continue
|
|
79
|
+
[[ -z "$repo" ]] && continue
|
|
80
|
+
|
|
81
|
+
local owner=$(echo "$repo" | cut -d'/' -f1)
|
|
82
|
+
local repo_name=$(echo "$repo" | cut -d'/' -f2)
|
|
83
|
+
|
|
84
|
+
echo "📋 检查仓库: $repo" >&2
|
|
85
|
+
|
|
86
|
+
# 获取开放的 PR(只获取前 10 个)
|
|
87
|
+
local pr_list=$(curl -s -H "Authorization: Bearer $GITCODE_API_TOKEN" \
|
|
88
|
+
"https://api.gitcode.com/api/v5/repos/$owner/$repo_name/pulls?state=opened&per_page=10")
|
|
89
|
+
|
|
90
|
+
# 提取 PR 编号
|
|
91
|
+
local pr_numbers=$(echo "$pr_list" | grep -o '"number":[0-9]*' | grep -o '[0-9]*')
|
|
92
|
+
|
|
93
|
+
for pr_number in $pr_numbers; do
|
|
94
|
+
if ! is_reviewed "$repo" "$pr_number"; then
|
|
95
|
+
# 找到未审查的 PR
|
|
96
|
+
echo "$repo|$pr_number"
|
|
97
|
+
return 0
|
|
98
|
+
fi
|
|
99
|
+
done
|
|
100
|
+
done < "$CONFIG_FILE"
|
|
101
|
+
|
|
102
|
+
# 没有找到需要审查的 PR
|
|
103
|
+
return 1
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# 主函数
|
|
107
|
+
main() {
|
|
108
|
+
echo "🤖 CANN 自动审查(单次模式)"
|
|
109
|
+
echo "================================"
|
|
110
|
+
echo "开始时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
111
|
+
echo ""
|
|
112
|
+
|
|
113
|
+
# 检查配置文件
|
|
114
|
+
if [ ! -f "$CONFIG_FILE" ]; then
|
|
115
|
+
echo "❌ 错误: 未配置审查仓库"
|
|
116
|
+
echo "请创建配置文件: config/repos.conf"
|
|
117
|
+
exit 1
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# 查找下一个需要审查的 PR
|
|
121
|
+
echo "🔍 查找下一个需要审查的 PR..."
|
|
122
|
+
local result=$(find_next_pr)
|
|
123
|
+
|
|
124
|
+
if [ -z "$result" ]; then
|
|
125
|
+
echo ""
|
|
126
|
+
echo "✅ 所有 PR 都已审查完毕"
|
|
127
|
+
echo "没有需要审查的 PR"
|
|
128
|
+
exit 0
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# 解析结果
|
|
132
|
+
local repo=$(echo "$result" | cut -d'|' -f1)
|
|
133
|
+
local pr_number=$(echo "$result" | cut -d'|' -f2)
|
|
134
|
+
|
|
135
|
+
echo ""
|
|
136
|
+
echo "📝 找到需要审查的 PR:"
|
|
137
|
+
echo " 仓库: $repo"
|
|
138
|
+
echo " PR: #$pr_number"
|
|
139
|
+
echo ""
|
|
140
|
+
|
|
141
|
+
# 获取 PR 详情
|
|
142
|
+
local owner=$(echo "$repo" | cut -d'/' -f1)
|
|
143
|
+
local repo_name=$(echo "$repo" | cut -d'/' -f2)
|
|
144
|
+
|
|
145
|
+
local pr_info=$(curl -s -H "Authorization: Bearer $GITCODE_API_TOKEN" \
|
|
146
|
+
"https://api.gitcode.com/api/v5/repos/$owner/$repo_name/pulls/$pr_number")
|
|
147
|
+
|
|
148
|
+
local title=$(echo "$pr_info" | grep -o '"title":"[^"]*"' | head -1 | cut -d'"' -f4)
|
|
149
|
+
local author=$(echo "$pr_info" | grep -o '"user":{[^}]*"name":"[^"]*"' | grep -o '"name":"[^"]*"' | cut -d'"' -f4)
|
|
150
|
+
local html_url=$(echo "$pr_info" | grep -o '"html_url":"[^"]*"' | cut -d'"' -f4)
|
|
151
|
+
|
|
152
|
+
echo " 标题: $title"
|
|
153
|
+
echo " 作者: $author"
|
|
154
|
+
echo " 链接: $html_url"
|
|
155
|
+
echo ""
|
|
156
|
+
|
|
157
|
+
# 标记为已审查(避免重复)
|
|
158
|
+
mark_reviewed "$repo" "$pr_number"
|
|
159
|
+
|
|
160
|
+
echo "✅ 已标记为待审查"
|
|
161
|
+
echo ""
|
|
162
|
+
echo "💡 提示: 这个 PR 已加入审查队列"
|
|
163
|
+
echo " 链接: $html_url"
|
|
164
|
+
echo ""
|
|
165
|
+
echo "下次定时任务触发时,会自动审查下一个 PR"
|
|
166
|
+
echo ""
|
|
167
|
+
echo "================================"
|
|
168
|
+
echo "结束时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# 运行主函数
|
|
172
|
+
main
|
package/auto-review.sh
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CANN 自动审查脚本
|
|
3
|
+
# 用于定时任务,自动审查指定仓库的开放 PR
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
CONFIG_FILE="$SCRIPT_DIR/config/repos.conf"
|
|
7
|
+
REVIEW_STATE_FILE="$SCRIPT_DIR/.review-state.json"
|
|
8
|
+
|
|
9
|
+
# 加载配置
|
|
10
|
+
load_config() {
|
|
11
|
+
# 优先级:环境变量 > 配置文件
|
|
12
|
+
if [ -n "$CANN_REVIEW_REPOS" ]; then
|
|
13
|
+
REPOS=$(echo "$CANN_REVIEW_REPOS" | tr ',' ' ')
|
|
14
|
+
elif [ -f "$CONFIG_FILE" ]; then
|
|
15
|
+
REPOS=$(grep -v '^#' "$CONFIG_FILE" | grep -v '^$' | tr '\n' ' ')
|
|
16
|
+
else
|
|
17
|
+
echo "错误: 未配置需要审查的仓库"
|
|
18
|
+
echo ""
|
|
19
|
+
echo "配置方法:"
|
|
20
|
+
echo " 1. 复制配置模板:"
|
|
21
|
+
echo " cp $SCRIPT_DIR/config/repos.conf.example $CONFIG_FILE"
|
|
22
|
+
echo ""
|
|
23
|
+
echo " 2. 编辑 $CONFIG_FILE"
|
|
24
|
+
echo " 添加需要审查的仓库(格式: owner/repo)"
|
|
25
|
+
echo ""
|
|
26
|
+
echo " 3. 或设置环境变量:"
|
|
27
|
+
echo " export CANN_REVIEW_REPOS='cann/runtime,cann/compiler'"
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# 加载审查状态
|
|
33
|
+
load_review_state() {
|
|
34
|
+
if [ -f "$REVIEW_STATE_FILE" ]; then
|
|
35
|
+
cat "$REVIEW_STATE_FILE"
|
|
36
|
+
else
|
|
37
|
+
echo '{}'
|
|
38
|
+
fi
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# 保存审查状态
|
|
42
|
+
save_review_state() {
|
|
43
|
+
echo "$1" > "$REVIEW_STATE_FILE"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# 检查是否已审查
|
|
47
|
+
is_reviewed() {
|
|
48
|
+
local repo=$1
|
|
49
|
+
local pr_number=$2
|
|
50
|
+
local state=$(load_review_state)
|
|
51
|
+
|
|
52
|
+
if echo "$state" | grep -q "\"$repo#$pr_number\""; then
|
|
53
|
+
return 0
|
|
54
|
+
else
|
|
55
|
+
return 1
|
|
56
|
+
fi
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# 标记为已审查
|
|
60
|
+
mark_reviewed() {
|
|
61
|
+
local repo=$1
|
|
62
|
+
local pr_number=$2
|
|
63
|
+
local result=$3
|
|
64
|
+
|
|
65
|
+
local state=$(load_review_state)
|
|
66
|
+
local timestamp=$(date +%s)
|
|
67
|
+
|
|
68
|
+
# 使用 Python 更新 JSON
|
|
69
|
+
python3 << EOF
|
|
70
|
+
import json
|
|
71
|
+
import sys
|
|
72
|
+
|
|
73
|
+
state = json.loads('''$state''')
|
|
74
|
+
key = "$repo#$pr_number"
|
|
75
|
+
state[key] = {
|
|
76
|
+
"reviewed_at": $timestamp,
|
|
77
|
+
"result": "$result"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
with open('$REVIEW_STATE_FILE', 'w') as f:
|
|
81
|
+
json.dump(state, f, indent=2)
|
|
82
|
+
EOF
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# 审查单个仓库
|
|
86
|
+
review_repo() {
|
|
87
|
+
local repo=$1
|
|
88
|
+
local owner=$(echo "$repo" | cut -d'/' -f1)
|
|
89
|
+
local repo_name=$(echo "$repo" | cut -d'/' -f2)
|
|
90
|
+
|
|
91
|
+
echo "📋 审查仓库: $repo"
|
|
92
|
+
|
|
93
|
+
# 获取开放的 PR 列表
|
|
94
|
+
local pr_list=$("$SCRIPT_DIR/gitcode-api.sh" list-prs "$owner" "$repo_name" 2>/dev/null)
|
|
95
|
+
|
|
96
|
+
if [ -z "$pr_list" ] || echo "$pr_list" | grep -q "error\|404"; then
|
|
97
|
+
echo " ⚠️ 无法获取 PR 列表或没有开放的 PR"
|
|
98
|
+
return
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# 统计
|
|
102
|
+
local total=0
|
|
103
|
+
local reviewed=0
|
|
104
|
+
local skipped=0
|
|
105
|
+
|
|
106
|
+
# 提取 PR 编号
|
|
107
|
+
local pr_numbers=$(echo "$pr_list" | grep -o '"number":[0-9]*' | grep -o '[0-9]*')
|
|
108
|
+
|
|
109
|
+
for pr_number in $pr_numbers; do
|
|
110
|
+
total=$((total + 1))
|
|
111
|
+
|
|
112
|
+
# 检查是否已审查
|
|
113
|
+
if [ $(is_reviewed "$repo" "$pr_number") -eq 0 ]; then
|
|
114
|
+
echo " ⏭️ PR #$pr_number - 已审查,跳过"
|
|
115
|
+
skipped=$((skipped + 1))
|
|
116
|
+
continue
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
echo " 🔍 审查 PR #$pr_number ..."
|
|
120
|
+
|
|
121
|
+
# 调用审查技能
|
|
122
|
+
# 这里可以调用 OpenClaw 的 API 或者直接使用技能
|
|
123
|
+
# 为了简单起见,这里只是标记为已审查
|
|
124
|
+
# 实际使用时应该调用技能的审查逻辑
|
|
125
|
+
|
|
126
|
+
# 标记为已审查
|
|
127
|
+
mark_reviewed "$repo" "$pr_number" "pending"
|
|
128
|
+
reviewed=$((reviewed + 1))
|
|
129
|
+
|
|
130
|
+
echo " ✅ PR #$pr_number - 已加入审查队列"
|
|
131
|
+
done
|
|
132
|
+
|
|
133
|
+
echo " 📊 统计: 总计 $total, 已审查 $reviewed, 跳过 $skipped"
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# 主函数
|
|
137
|
+
main() {
|
|
138
|
+
echo "🤖 CANN 自动审查"
|
|
139
|
+
echo "===================="
|
|
140
|
+
echo "开始时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
141
|
+
echo ""
|
|
142
|
+
|
|
143
|
+
# 加载配置
|
|
144
|
+
load_config
|
|
145
|
+
|
|
146
|
+
echo "📂 配置的仓库:"
|
|
147
|
+
for repo in $REPOS; do
|
|
148
|
+
echo " - $repo"
|
|
149
|
+
done
|
|
150
|
+
echo ""
|
|
151
|
+
|
|
152
|
+
# 统计
|
|
153
|
+
local total_repos=${#REPOS[@]}
|
|
154
|
+
local processed=0
|
|
155
|
+
|
|
156
|
+
# 审查每个仓库
|
|
157
|
+
for repo in $REPOS; do
|
|
158
|
+
processed=$((processed + 1))
|
|
159
|
+
echo "[$processed/$total_repos] 审查仓库: $repo"
|
|
160
|
+
review_repo "$repo"
|
|
161
|
+
echo ""
|
|
162
|
+
done
|
|
163
|
+
|
|
164
|
+
echo "===================="
|
|
165
|
+
echo "✅ 自动审查完成"
|
|
166
|
+
echo "结束时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
# 运行主函数
|
|
170
|
+
main
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
检查 GitCode 仓库中@你的评论并自动审查相关PR
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
# 配置
|
|
15
|
+
TOKEN = "5_EtXLq3jGyQvb6tWwrN3byz"
|
|
16
|
+
API_BASE = "https://api.gitcode.com/api/v5"
|
|
17
|
+
USER = "newstarzj"
|
|
18
|
+
STATE_FILE = Path.home() / ".openclaw/workspace/skills/cann-review/.mention-state.json"
|
|
19
|
+
REPOS = [
|
|
20
|
+
"cann/runtime",
|
|
21
|
+
"cann/oam-tools",
|
|
22
|
+
"cann/oam-tools-diag"
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
def log(msg):
|
|
26
|
+
"""带时间戳的日志"""
|
|
27
|
+
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
28
|
+
print(f"[{timestamp}] {msg}")
|
|
29
|
+
|
|
30
|
+
def init_state():
|
|
31
|
+
"""初始化状态文件"""
|
|
32
|
+
if not STATE_FILE.exists():
|
|
33
|
+
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
state = {"checked_comments": [], "last_check": ""}
|
|
35
|
+
STATE_FILE.write_text(json.dumps(state, indent=2))
|
|
36
|
+
return json.loads(STATE_FILE.read_text())
|
|
37
|
+
|
|
38
|
+
def save_state(state):
|
|
39
|
+
"""保存状态文件"""
|
|
40
|
+
STATE_FILE.write_text(json.dumps(state, indent=2))
|
|
41
|
+
|
|
42
|
+
def api_get(url):
|
|
43
|
+
"""调用 GitCode API"""
|
|
44
|
+
# 使用access_token参数而非Bearer token,因为Bearer方式会截断评论列表
|
|
45
|
+
sep = '&' if '?' in url else '?'
|
|
46
|
+
cmd = f'curl -s "{url}{sep}access_token={TOKEN}&per_page=100"'
|
|
47
|
+
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
|
48
|
+
try:
|
|
49
|
+
return json.loads(result.stdout)
|
|
50
|
+
except:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
def check_repo_mentions(repo, state):
|
|
54
|
+
"""检查仓库中的@评论"""
|
|
55
|
+
log(f"检查仓库 {repo} 中的@评论...")
|
|
56
|
+
|
|
57
|
+
# 获取开放的PR
|
|
58
|
+
prs_data = api_get(f"{API_BASE}/repos/{repo}/pulls?state=open&per_page=50")
|
|
59
|
+
if not prs_data or not isinstance(prs_data, list):
|
|
60
|
+
log(f"仓库 {repo} 没有开放的PR或获取失败")
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
new_mentions = []
|
|
64
|
+
|
|
65
|
+
for pr in prs_data:
|
|
66
|
+
pr_num = pr['number']
|
|
67
|
+
|
|
68
|
+
# 获取PR评论
|
|
69
|
+
comments = api_get(f"{API_BASE}/repos/{repo}/pulls/{pr_num}/comments")
|
|
70
|
+
if not comments or not isinstance(comments, list):
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
# 检查评论中是否@了用户(支持多种格式)
|
|
74
|
+
for comment in comments:
|
|
75
|
+
body = comment.get('body', '')
|
|
76
|
+
|
|
77
|
+
# 检查多种@格式
|
|
78
|
+
has_mention = False
|
|
79
|
+
if f'@{USER}' in body:
|
|
80
|
+
has_mention = True
|
|
81
|
+
elif f'[@{USER}]' in body:
|
|
82
|
+
has_mention = True
|
|
83
|
+
elif f'@{USER}' in body:
|
|
84
|
+
has_mention = True
|
|
85
|
+
|
|
86
|
+
if has_mention:
|
|
87
|
+
comment_id = comment.get('id')
|
|
88
|
+
|
|
89
|
+
# 检查是否已经处理过
|
|
90
|
+
if comment_id in state['checked_comments']:
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
# 记录新的@评论
|
|
94
|
+
new_mentions.append({
|
|
95
|
+
'repo': repo,
|
|
96
|
+
'pr_num': pr_num,
|
|
97
|
+
'pr_title': pr.get('title', 'N/A'),
|
|
98
|
+
'comment_id': comment_id,
|
|
99
|
+
'comment_user': comment.get('user', {}).get('name', 'Unknown'),
|
|
100
|
+
'comment_time': comment.get('created_at', 'N/A'),
|
|
101
|
+
'comment_body': body[:100],
|
|
102
|
+
'url': f"https://gitcode.com/{repo}/pull/{pr_num}"
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
# 标记为已处理
|
|
106
|
+
state['checked_comments'].append(comment_id)
|
|
107
|
+
log(f"发现新的@评论: PR #{pr_num}, 评论者: {comment.get('user', {}).get('name', 'Unknown')}")
|
|
108
|
+
|
|
109
|
+
log(f"仓库 {repo} 发现 {len(new_mentions)} 个新的@评论")
|
|
110
|
+
return new_mentions
|
|
111
|
+
|
|
112
|
+
def main():
|
|
113
|
+
"""主函数"""
|
|
114
|
+
log("========== 开始检查@评论 ==========")
|
|
115
|
+
|
|
116
|
+
# 初始化状态
|
|
117
|
+
state = init_state()
|
|
118
|
+
|
|
119
|
+
# 检查所有仓库
|
|
120
|
+
all_mentions = []
|
|
121
|
+
for repo in REPOS:
|
|
122
|
+
mentions = check_repo_mentions(repo, state)
|
|
123
|
+
all_mentions.extend(mentions)
|
|
124
|
+
|
|
125
|
+
# 更新状态
|
|
126
|
+
state['last_check'] = datetime.now().isoformat()
|
|
127
|
+
save_state(state)
|
|
128
|
+
|
|
129
|
+
# 输出结果供后续处理
|
|
130
|
+
if all_mentions:
|
|
131
|
+
log(f"发现 {len(all_mentions)} 个需要审查的PR")
|
|
132
|
+
# 输出JSON格式供 agent 解析
|
|
133
|
+
print("\n=== MENTIONS_JSON ===")
|
|
134
|
+
print(json.dumps(all_mentions, ensure_ascii=False))
|
|
135
|
+
print("=== END_MENTIONS_JSON ===\n")
|
|
136
|
+
else:
|
|
137
|
+
log("没有发现新的@评论")
|
|
138
|
+
|
|
139
|
+
log("========== 检查完成 ==========")
|
|
140
|
+
|
|
141
|
+
return all_mentions
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
mentions = main()
|
|
145
|
+
sys.exit(0 if mentions else 0)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#
|
|
4
|
+
# 检查@你的评论并自动审查相关PR
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
set -e
|
|
8
|
+
|
|
9
|
+
TOKEN="5_EtXLq3jGyQvb6tWwrN3byz"
|
|
10
|
+
API_BASE="https://api.gitcode.com/api/v5"
|
|
11
|
+
USER="newstarzj"
|
|
12
|
+
STATE_FILE="$HOME/.openclaw/workspace/skills/cann-review/.mention-state.json"
|
|
13
|
+
|
|
14
|
+
# 颜色输出
|
|
15
|
+
RED='\033[0;31m'
|
|
16
|
+
GREEN='\033[0;32m'
|
|
17
|
+
YELLOW='\033[1;33m'
|
|
18
|
+
NC='\033[0m' # No Color
|
|
19
|
+
|
|
20
|
+
log() {
|
|
21
|
+
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
log_error() {
|
|
25
|
+
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ERROR:${NC} $1"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
log_warn() {
|
|
29
|
+
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] WARN:${NC} $1"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# 初始化状态文件
|
|
33
|
+
init_state() {
|
|
34
|
+
if [ ! -f "$STATE_FILE" ]; then
|
|
35
|
+
echo '{"checked_comments": [], "last_check": ""}' > "$STATE_FILE"
|
|
36
|
+
fi
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# 检查仓库中的@评论
|
|
40
|
+
check_repo_mentions() {
|
|
41
|
+
local repo=$1
|
|
42
|
+
log "检查仓库 $repo 中的@评论..."
|
|
43
|
+
|
|
44
|
+
# 获取开放的PR
|
|
45
|
+
local prs=$(curl -s -H "Authorization: Bearer $TOKEN" \
|
|
46
|
+
"$API_BASE/repos/$repo/pulls?state=open&per_page=50" | \
|
|
47
|
+
jq -r '.[].number')
|
|
48
|
+
|
|
49
|
+
if [ -z "$prs" ]; then
|
|
50
|
+
log "仓库 $repo 没有开放的PR"
|
|
51
|
+
return
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
local count=0
|
|
55
|
+
for pr_num in $prs; do
|
|
56
|
+
# 获取PR评论
|
|
57
|
+
local comments=$(curl -s -H "Authorization: Bearer $TOKEN" \
|
|
58
|
+
"$API_BASE/repos/$repo/pulls/$pr_num/comments")
|
|
59
|
+
|
|
60
|
+
# 检查评论中是否@了用户
|
|
61
|
+
if echo "$comments" | jq -e ".[] | select(.body | contains(\"@$USER\"))" > /dev/null 2>&1; then
|
|
62
|
+
# 获取评论详情
|
|
63
|
+
local comment_info=$(echo "$comments" | jq -r ".[] | select(.body | contains(\"@$USER\")) | {id: .id, body: .body, created_at: .created_at, user: .user.login} | @base64" | head -1)
|
|
64
|
+
|
|
65
|
+
if [ -n "$comment_info" ]; then
|
|
66
|
+
local comment_data=$(echo "$comment_info" | base64 -d)
|
|
67
|
+
local comment_id=$(echo "$comment_data" | jq -r '.id')
|
|
68
|
+
local comment_time=$(echo "$comment_data" | jq -r '.created_at')
|
|
69
|
+
local comment_user=$(echo "$comment_data" | jq -r '.user')
|
|
70
|
+
|
|
71
|
+
# 检查是否已经处理过
|
|
72
|
+
if jq -e ".checked_comments | index($comment_id)" "$STATE_FILE" > /dev/null 2>&1; then
|
|
73
|
+
log "评论 $comment_id 已处理过,跳过"
|
|
74
|
+
continue
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
log "发现新的@评论: PR #$pr_num, 评论者: $comment_user"
|
|
78
|
+
|
|
79
|
+
# 输出PR信息供后续处理
|
|
80
|
+
echo "$repo|$pr_num|$comment_id|$comment_time"
|
|
81
|
+
|
|
82
|
+
# 记录到状态文件
|
|
83
|
+
local temp_file=$(mktemp)
|
|
84
|
+
jq ".checked_comments += [$comment_id] | .last_check = \"$(date -Iseconds)\"" "$STATE_FILE" > "$temp_file"
|
|
85
|
+
mv "$temp_file" "$STATE_FILE"
|
|
86
|
+
|
|
87
|
+
count=$((count + 1))
|
|
88
|
+
fi
|
|
89
|
+
fi
|
|
90
|
+
done
|
|
91
|
+
|
|
92
|
+
log "仓库 $repo 发现 $count 个新的@评论"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# 主函数
|
|
96
|
+
main() {
|
|
97
|
+
log "========== 开始检查@评论 =========="
|
|
98
|
+
|
|
99
|
+
init_state
|
|
100
|
+
|
|
101
|
+
# 检查配置的仓库
|
|
102
|
+
REPOS=(
|
|
103
|
+
"cann/runtime"
|
|
104
|
+
"cann/oam-tools"
|
|
105
|
+
"cann/oam-tools-diag"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
local new_mentions=()
|
|
109
|
+
|
|
110
|
+
for repo in "${REPOS[@]}"; do
|
|
111
|
+
local mentions=$(check_repo_mentions "$repo")
|
|
112
|
+
if [ -n "$mentions" ]; then
|
|
113
|
+
new_mentions+=("$mentions")
|
|
114
|
+
fi
|
|
115
|
+
done
|
|
116
|
+
|
|
117
|
+
# 如果有新的@,输出供后续处理
|
|
118
|
+
if [ ${#new_mentions[@]} -gt 0 ]; then
|
|
119
|
+
log "发现 ${#new_mentions[@]} 个需要审查的PR"
|
|
120
|
+
printf '%s\n' "${new_mentions[@]}"
|
|
121
|
+
else
|
|
122
|
+
log "没有发现新的@评论"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
log "========== 检查完成 =========="
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
main "$@"
|