From 072df26850f3ef4df3d57bd25d55f7408ef2046c Mon Sep 17 00:00:00 2001 From: liuxiaotong Date: Fri, 6 Mar 2026 13:52:08 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=8D=87=E7=BA=A7=20auto-review=20workflow?= =?UTF-8?q?=EF=BC=9A=E5=BC=82=E6=AD=A5API=20+=20=E5=AE=A1=E6=9F=A5?= =?UTF-8?q?=E9=97=AD=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/auto-review.yml | 58 +++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/.github/workflows/auto-review.yml b/.github/workflows/auto-review.yml index ba86535..fec83cf 100644 --- a/.github/workflows/auto-review.yml +++ b/.github/workflows/auto-review.yml @@ -119,8 +119,8 @@ jobs: - summary: 审查总结 - comments: 具体意见数组(每条包含 file, line, comment)" - # 调用林锐审查 - RESPONSE=$(curl -s -X POST "https://crew.knowlyr.com/api/run/employee/code-reviewer" \ + # 调用林锐审查(异步 API:POST 返回 task_id,轮询等结果) + SUBMIT=$(curl -s -X POST "https://crew.knowlyr.com/run/employee/code-reviewer" \ -H "Authorization: Bearer ${CREW_API_TOKEN}" \ -H "Content-Type: application/json" \ -d "$(python3 -c " @@ -129,13 +129,47 @@ jobs: print(json.dumps({'task': task, 'format': 'json'})) " <<< "$TASK")") - echo "Review response received" - - # 解析返回结果,提取 JSON - RESULT=$(echo "$RESPONSE" | python3 -c " + TASK_ID=$(echo "$SUBMIT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('task_id',''))" 2>/dev/null) + if [ -z "$TASK_ID" ]; then + echo "Failed to submit review task: $SUBMIT" + RESULT='{"approved": false, "summary": "Failed to submit review task", "comments": []}' + else + echo "Review task submitted: $TASK_ID — polling for result..." + + # 轮询等待完成(最多 5 分钟) + POLL_TIMEOUT=300 + POLL_INTERVAL=10 + POLL_ELAPSED=0 + RESPONSE="" + + while [ $POLL_ELAPSED -lt $POLL_TIMEOUT ]; do + sleep $POLL_INTERVAL + POLL_ELAPSED=$((POLL_ELAPSED + POLL_INTERVAL)) + POLL_RESP=$(curl -s "https://crew.knowlyr.com/tasks/${TASK_ID}" \ + -H "Authorization: Bearer ${CREW_API_TOKEN}") + STATUS=$(echo "$POLL_RESP" | python3 -c "import json,sys; print(json.load(sys.stdin).get('status','unknown'))" 2>/dev/null) + + if [ "$STATUS" = "completed" ]; then + RESPONSE="$POLL_RESP" + echo "Review completed (${POLL_ELAPSED}s)" + break + elif [ "$STATUS" = "failed" ]; then + echo "Review task failed" + break + fi + echo " Still running... (${POLL_ELAPSED}s / ${POLL_TIMEOUT}s)" + done + + if [ -z "$RESPONSE" ]; then + echo "Review task timed out or failed" + RESULT='{"approved": false, "summary": "Review task timed out", "comments": []}' + else + # 解析返回结果:result.output 是林锐的文本输出,从中提取 JSON + RESULT=$(echo "$RESPONSE" | python3 -c " import json, sys, re data = json.load(sys.stdin) - text = data.get('result', data.get('output', '')) + result = data.get('result', {}) + text = result.get('output', '') if isinstance(result, dict) else str(result) # 尝试从文本中提取 JSON match = re.search(r'\{.*\}', text, re.DOTALL) if match: @@ -143,10 +177,12 @@ jobs: parsed = json.loads(match.group()) print(json.dumps(parsed)) except: - print(json.dumps({'approved': False, 'summary': text, 'comments': []})) + print(json.dumps({'approved': False, 'summary': text[:2000], 'comments': []})) else: - print(json.dumps({'approved': False, 'summary': text, 'comments': []})) + print(json.dumps({'approved': False, 'summary': text[:2000], 'comments': []})) " 2>/dev/null || echo '{"approved": false, "summary": "Failed to parse review response", "comments": []}') + fi + fi APPROVED=$(echo "$RESULT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('approved', False))") SUMMARY=$(echo "$RESULT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('summary', 'No summary'))") @@ -198,7 +234,7 @@ jobs: )" else echo "PR not approved — leaving review comments" - gh pr comment "$PR_NUMBER" --body "$(cat < Date: Fri, 6 Mar 2026 16:30:50 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20auto-review=20=E5=8F=AA=E5=9C=A8?= =?UTF-8?q?=E7=AC=AC1=E8=BD=AE=E6=B4=BE=E5=9B=9E=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E5=BE=AA=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/auto-review.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/auto-review.yml b/.github/workflows/auto-review.yml index fec83cf..4d4e1c7 100644 --- a/.github/workflows/auto-review.yml +++ b/.github/workflows/auto-review.yml @@ -243,6 +243,27 @@ jobs: EOF )" + # 审查不通过 — 只在第 1 轮自动派回修复(避免循环:审查→派单→修复→再审查→再派单) + if [ "$ROUND" -eq 1 ]; then + curl -s -X POST "https://crew.knowlyr.com/run/employee/ceo-assistant" \ + -H "Authorization: Bearer ${CREW_API_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$(python3 -c " + import json + task = '''PR #${PR_NUMBER} 第 ${ROUND} 轮审查不通过,需要派人修复。 + 仓库: ${REPO} + + 审查意见: + ${SUMMARY} + + ${COMMENTS} + + 请根据仓库和问题类型派合适的工程师修复,修完后推代码触发下一轮审查。''' + print(json.dumps({'task': task})) + ")" + echo "Auto-dispatch: review feedback sent to ceo-assistant" + fi + # 第 4 轮:需要人工介入 if [ "$ROUND" -ge 4 ]; then echo "Round ${ROUND}: escalating to human review" From 4e94379791b0fb5194932d7568f17bc217025101 Mon Sep 17 00:00:00 2001 From: liuxiaotong Date: Fri, 6 Mar 2026 17:07:47 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20auto-review=20diff=20=E4=BC=A0?= =?UTF-8?q?=E5=8F=82=E6=94=B9=E7=94=A8=E6=96=87=E4=BB=B6=E9=81=BF=E5=85=8D?= =?UTF-8?q?=20shell=20=E6=88=AA=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/auto-review.yml | 40 +++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/.github/workflows/auto-review.yml b/.github/workflows/auto-review.yml index 4d4e1c7..445ca7e 100644 --- a/.github/workflows/auto-review.yml +++ b/.github/workflows/auto-review.yml @@ -98,36 +98,47 @@ jobs: REPO=${{ github.repository }} ROUND=${{ steps.round.outputs.round }} - # 获取 PR diff - DIFF=$(gh pr diff "$PR_NUMBER" | head -c 50000) + # 获取 PR diff — 写文件避免 shell 变量截断特殊字符 + gh pr diff "$PR_NUMBER" | head -c 50000 > /tmp/pr_diff.txt # 获取 PR 信息 PR_TITLE=$(gh pr view "$PR_NUMBER" --json title -q '.title') PR_BODY=$(gh pr view "$PR_NUMBER" --json body -q '.body' | head -c 2000) - # 构建审查请求 - TASK="审查 PR #${PR_NUMBER} 的代码变更。仓库: ${REPO}。这是第 ${ROUND} 轮审查。 + # 用 python3 从文件读 diff 构建 JSON payload,避免 shell 展开问题 + PR_TITLE="$PR_TITLE" PR_BODY="$PR_BODY" REPO="$REPO" PR_NUMBER="$PR_NUMBER" ROUND="$ROUND" python3 << 'PYEOF' > /tmp/review_payload.json + import json, os - PR 标题: ${PR_TITLE} - PR 描述: ${PR_BODY} + with open('/tmp/pr_diff.txt') as f: + diff = f.read() + + repo = os.environ['REPO'] + pr_number = os.environ['PR_NUMBER'] + round_num = os.environ['ROUND'] + title = os.environ['PR_TITLE'] + body = os.environ['PR_BODY'] + + task = f"""审查 PR #{pr_number} 的代码变更。仓库: {repo}。这是第 {round_num} 轮审查。 + + PR 标题: {title} + PR 描述: {body} 代码变更 (diff): - ${DIFF} + {diff} 请以 JSON 格式返回审查结果,包含: - approved: true/false - summary: 审查总结 - - comments: 具体意见数组(每条包含 file, line, comment)" + - comments: 具体意见数组(每条包含 file, line, comment)""" + + print(json.dumps({'task': task, 'format': 'json'})) + PYEOF # 调用林锐审查(异步 API:POST 返回 task_id,轮询等结果) SUBMIT=$(curl -s -X POST "https://crew.knowlyr.com/run/employee/code-reviewer" \ -H "Authorization: Bearer ${CREW_API_TOKEN}" \ -H "Content-Type: application/json" \ - -d "$(python3 -c " - import json, sys - task = sys.stdin.read() - print(json.dumps({'task': task, 'format': 'json'})) - " <<< "$TASK")") + -d @/tmp/review_payload.json) TASK_ID=$(echo "$SUBMIT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('task_id',''))" 2>/dev/null) if [ -z "$TASK_ID" ]; then @@ -207,6 +218,9 @@ jobs: echo "$SUMMARY" > /tmp/review_summary.txt echo "$COMMENTS" > /tmp/review_comments.txt + # 清理临时文件 + rm -f /tmp/pr_diff.txt /tmp/review_payload.json + - name: Apply review result env: GH_TOKEN: ${{ github.token }} From 5955e62b92df4b65d0b717c226038be167741868 Mon Sep 17 00:00:00 2001 From: liuxiaotong Date: Fri, 6 Mar 2026 19:23:28 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20auto-review=20=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E5=8A=A0=E5=9B=BA=EF=BC=88=E5=91=BD=E4=BB=A4=E6=B3=A8=E5=85=A5?= =?UTF-8?q?+token=E6=A0=A1=E9=AA=8C+=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/auto-review.yml | 74 +++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 23 deletions(-) diff --git a/.github/workflows/auto-review.yml b/.github/workflows/auto-review.yml index 445ca7e..6c4fc6a 100644 --- a/.github/workflows/auto-review.yml +++ b/.github/workflows/auto-review.yml @@ -233,6 +233,11 @@ jobs: SUMMARY=$(cat /tmp/review_summary.txt) COMMENTS=$(cat /tmp/review_comments.txt) + # [Fix #2] CREW_API_TOKEN 校验 + if [ -z "${CREW_API_TOKEN}" ]; then + echo "::warning::CREW_API_TOKEN not configured — skipping auto-dispatch" + fi + # 添加轮次 label gh pr edit "$PR_NUMBER" --add-label "review-round-${ROUND}" 2>/dev/null || \ gh label create "review-round-${ROUND}" --color "0E8A16" 2>/dev/null && \ @@ -257,25 +262,37 @@ jobs: EOF )" - # 审查不通过 — 只在第 1 轮自动派回修复(避免循环:审查→派单→修复→再审查→再派单) - if [ "$ROUND" -eq 1 ]; then - curl -s -X POST "https://crew.knowlyr.com/run/employee/ceo-assistant" \ - -H "Authorization: Bearer ${CREW_API_TOKEN}" \ - -H "Content-Type: application/json" \ - -d "$(python3 -c " - import json - task = '''PR #${PR_NUMBER} 第 ${ROUND} 轮审查不通过,需要派人修复。 - 仓库: ${REPO} + # 审查不通过 — 只在第 1 轮自动派回修复(避免循环:审查->派单->修复->再审查->再派单) + if [ "$ROUND" -eq 1 ] && [ -n "${CREW_API_TOKEN}" ]; then + # [Fix #1] 用环境变量+文件方式传参,避免命令注入 + PR_NUMBER="$PR_NUMBER" ROUND="$ROUND" REPO="$REPO" python3 << 'PYEOF' > /tmp/dispatch_payload.json + import json, os + summary = open('/tmp/review_summary.txt').read() + comments = open('/tmp/review_comments.txt').read() + task = f"""PR #{os.environ['PR_NUMBER']} 第 {os.environ['ROUND']} 轮审查不通过,需要派人修复。 + 仓库: {os.environ['REPO']} 审查意见: - ${SUMMARY} + {summary} - ${COMMENTS} + {comments} - 请根据仓库和问题类型派合适的工程师修复,修完后推代码触发下一轮审查。''' + 请根据仓库和问题类型派合适的工程师修复,修完后推代码触发下一轮审查。""" print(json.dumps({'task': task})) - ")" - echo "Auto-dispatch: review feedback sent to ceo-assistant" + PYEOF + + # [Fix #3] dispatch curl 加 HTTP 状态码检查 + DISPATCH_RESP=$(curl -s -w "\n%{http_code}" -X POST "https://crew.knowlyr.com/run/employee/ceo-assistant" \ + -H "Authorization: Bearer ${CREW_API_TOKEN}" \ + -H "Content-Type: application/json" \ + -d @/tmp/dispatch_payload.json) + HTTP_CODE=$(echo "$DISPATCH_RESP" | tail -n1) + if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ]; then + echo "::warning::Auto-dispatch failed (HTTP $HTTP_CODE)" + else + echo "Auto-dispatch: review feedback sent to ceo-assistant" + fi + rm -f /tmp/dispatch_payload.json fi # 第 4 轮:需要人工介入 @@ -284,15 +301,26 @@ jobs: gh label create "needs-human-review" --color "D93F0B" 2>/dev/null || true gh pr edit "$PR_NUMBER" --add-label "needs-human-review" - # 通知 Kai - curl -s -X POST "https://crew.knowlyr.com/run/employee/ceo-assistant" \ - -H "Authorization: Bearer ${CREW_API_TOKEN}" \ - -H "Content-Type: application/json" \ - -d "$(python3 -c " - import json - task = 'PR #${PR_NUMBER} 经过 ${ROUND} 轮审查仍未通过,需要 Kai 介入。仓库: ${REPO}。请通过飞书通知 Kai。' + if [ -n "${CREW_API_TOKEN}" ]; then + # [Fix #1] 用环境变量+文件方式传参,避免命令注入 + PR_NUMBER="$PR_NUMBER" ROUND="$ROUND" REPO="$REPO" python3 << 'PYEOF' > /tmp/escalate_payload.json + import json, os + task = f"PR #{os.environ['PR_NUMBER']} 经过 {os.environ['ROUND']} 轮审查仍未通过,需要 Kai 介入。仓库: {os.environ['REPO']}。请通过飞书通知 Kai。" print(json.dumps({'task': task})) - ")" - echo "Human review notification sent" + PYEOF + + # [Fix #3] dispatch curl 加 HTTP 状态码检查 + ESCALATE_RESP=$(curl -s -w "\n%{http_code}" -X POST "https://crew.knowlyr.com/run/employee/ceo-assistant" \ + -H "Authorization: Bearer ${CREW_API_TOKEN}" \ + -H "Content-Type: application/json" \ + -d @/tmp/escalate_payload.json) + HTTP_CODE=$(echo "$ESCALATE_RESP" | tail -n1) + if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ]; then + echo "::warning::Human review notification failed (HTTP $HTTP_CODE)" + else + echo "Human review notification sent" + fi + rm -f /tmp/escalate_payload.json + fi fi fi