373 lines
14 KiB
YAML
373 lines
14 KiB
YAML
# ═══════════════════════════════════════════════
|
||
# 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
|
||
# 📜 Copyright: 国作登字-2026-A-00037559
|
||
# ═══════════════════════════════════════════════
|
||
# .github/workflows/tianyan-nightly-scan.yml
|
||
# 🌙 天眼夜间自动修复引擎 v3.0
|
||
#
|
||
# Phase A: 天眼夜间巡查(扫描过去24h失败Workflow)
|
||
# Phase B: 报错智能分析(Gemini API)
|
||
# Phase C: 铸渊自动修复(生成修复代码)
|
||
# Phase D: 天眼审核(H1-H9 检查清单)
|
||
# Phase E: 创建 PR(等待冰朔晨间合并)
|
||
# Phase F: 更新仪表盘
|
||
#
|
||
# 指令编号: ZY-TIANYAN-AUTOFIX-2026-0324-001
|
||
# AG-ZY-087
|
||
|
||
name: "🌙 天眼夜间自动修复引擎 v3.0"
|
||
|
||
on:
|
||
schedule:
|
||
- cron: '0 15 * * *' # 23:00 CST = 15:00 UTC
|
||
workflow_dispatch:
|
||
inputs:
|
||
force_scan:
|
||
description: '强制扫描(忽略时间窗口)'
|
||
type: boolean
|
||
default: false
|
||
repository_dispatch:
|
||
types: [watchdog-retry]
|
||
|
||
permissions:
|
||
actions: read
|
||
contents: write
|
||
pull-requests: write
|
||
checks: read
|
||
|
||
env:
|
||
REPAIR_BRANCH_PREFIX: "fix/auto-repair"
|
||
MAX_FILES_PER_REPAIR: 5
|
||
MAX_LINES_PER_FILE: 50
|
||
MAX_RETRY_ROUNDS: 2
|
||
GEMINI_MODEL: "gemini-2.0-flash"
|
||
PROTECTED_PATTERNS: "tianyan-*.yml,CODEOWNERS,.github/FUNDING.yml"
|
||
|
||
jobs:
|
||
# ========================================
|
||
# Phase A: 天眼夜间巡查
|
||
# ========================================
|
||
nightly-scan:
|
||
runs-on: ubuntu-latest
|
||
outputs:
|
||
has_errors: ${{ steps.scan.outputs.has_errors }}
|
||
error_report: ${{ steps.scan.outputs.error_report }}
|
||
steps:
|
||
- name: "🔭 扫描过去24小时 Workflow 运行记录"
|
||
id: scan
|
||
env:
|
||
GH_TOKEN: ${{ secrets.DEPLOY_GITHUB_TOKEN }}
|
||
run: |
|
||
echo "[天眼 v3.0] Phase A 启动 — 夜间巡查扫描"
|
||
echo "═══════════════════════════════════════════"
|
||
SINCE=$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ)
|
||
echo "📅 扫描起始时间: $SINCE"
|
||
|
||
# 获取失败的 workflow runs
|
||
FAILED_RUNS=$(gh api \
|
||
"repos/${{ github.repository }}/actions/runs?created=>$SINCE&status=failure&per_page=50" \
|
||
--jq '.workflow_runs' 2>/dev/null || echo '[]')
|
||
|
||
FAIL_COUNT=$(echo "$FAILED_RUNS" | jq 'length')
|
||
TOTAL_RUNS=$(gh api \
|
||
"repos/${{ github.repository }}/actions/runs?created=>$SINCE&per_page=1" \
|
||
--jq '.total_count' 2>/dev/null || echo '0')
|
||
|
||
echo "📊 过去24小时统计:"
|
||
echo " 总运行数: $TOTAL_RUNS"
|
||
echo " 失败运行: $FAIL_COUNT"
|
||
|
||
if [ "$FAIL_COUNT" -eq 0 ] || [ "$FAIL_COUNT" = "null" ]; then
|
||
echo "has_errors=false" >> $GITHUB_OUTPUT
|
||
echo "✅ 今日无异常"
|
||
else
|
||
echo "has_errors=true" >> $GITHUB_OUTPUT
|
||
|
||
# 生成结构化错误报告
|
||
ERROR_JSON=$(echo "$FAILED_RUNS" | jq -c '[.[] | {
|
||
workflow: .name,
|
||
run_id: .id,
|
||
conclusion: .conclusion,
|
||
html_url: .html_url,
|
||
created_at: .created_at,
|
||
head_branch: .head_branch
|
||
}]')
|
||
|
||
# Use heredoc for multiline output
|
||
echo "error_report<<TIANYAN_EOF" >> $GITHUB_OUTPUT
|
||
echo "$ERROR_JSON" >> $GITHUB_OUTPUT
|
||
echo "TIANYAN_EOF" >> $GITHUB_OUTPUT
|
||
|
||
echo "⚠️ 发现 $FAIL_COUNT 个失败运行,进入 Phase B"
|
||
fi
|
||
|
||
# ========================================
|
||
# Phase B+C+D: 分析 → 修复 → 审核
|
||
# ========================================
|
||
analyze-and-repair:
|
||
needs: nightly-scan
|
||
if: needs.nightly-scan.outputs.has_errors == 'true'
|
||
runs-on: ubuntu-latest
|
||
outputs:
|
||
repair_branch: ${{ steps.repair.outputs.branch_name }}
|
||
repair_summary: ${{ steps.create_summary.outputs.summary }}
|
||
pr_needed: ${{ steps.review.outputs.pr_needed }}
|
||
fix_count: ${{ steps.review.outputs.fix_count }}
|
||
overall_risk: ${{ steps.review.outputs.overall_risk }}
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: '20'
|
||
|
||
- name: "🧠 Phase B — Gemini 报错分析"
|
||
id: analyze
|
||
env:
|
||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||
ERROR_REPORT: ${{ needs.nightly-scan.outputs.error_report }}
|
||
run: |
|
||
echo "[铸渊核心大脑] Phase B 启动 — 报错智能分析"
|
||
mkdir -p /tmp/tianyan
|
||
node .github/scripts/tianyan-analyze.js
|
||
|
||
- name: "⚒️ Phase C — 铸渊自动修复"
|
||
id: repair
|
||
env:
|
||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||
GH_TOKEN: ${{ secrets.DEPLOY_GITHUB_TOKEN }}
|
||
run: |
|
||
echo "[铸渊核心大脑] Phase C 启动 — 自动修复"
|
||
|
||
# Check if there are fixable errors
|
||
FIXABLE=$(cat /tmp/tianyan/analysis.json | jq '.auto_fixable // 0')
|
||
if [ "$FIXABLE" -eq 0 ]; then
|
||
echo "⚠️ 没有可自动修复的错误"
|
||
echo "branch_name=" >> $GITHUB_OUTPUT
|
||
exit 0
|
||
fi
|
||
|
||
# Create repair branch (use timestamp to avoid conflicts)
|
||
BRANCH="${REPAIR_BRANCH_PREFIX}-$(date +%Y%m%d)-$(date +%H%M%S)"
|
||
git config user.name "zhuyuan-bot"
|
||
git config user.email "zhuyuan@guanghulab.com"
|
||
git checkout -b "$BRANCH"
|
||
|
||
# Run repair script
|
||
node .github/scripts/tianyan-repair.js
|
||
|
||
# Check if there are changes to push
|
||
if git diff --quiet && git diff --cached --quiet; then
|
||
echo "⚠️ 修复脚本未产生变更"
|
||
echo "branch_name=" >> $GITHUB_OUTPUT
|
||
else
|
||
git push origin "$BRANCH" || echo "⚠️ Push failed"
|
||
echo "branch_name=$BRANCH" >> $GITHUB_OUTPUT
|
||
fi
|
||
|
||
- name: "🔭 Phase D — 天眼审核修复代码"
|
||
id: review
|
||
if: steps.repair.outputs.branch_name != ''
|
||
run: |
|
||
echo "[天眼 v3.0] Phase D 启动 — 审核修复代码"
|
||
node .github/scripts/tianyan-review.js
|
||
|
||
# Read review result
|
||
if [ -f /tmp/tianyan/review-result.json ]; then
|
||
PR_NEEDED=$(cat /tmp/tianyan/review-result.json | jq -r '.pr_needed // false')
|
||
echo "pr_needed=$PR_NEEDED" >> $GITHUB_OUTPUT
|
||
|
||
FIX_COUNT=$(cat /tmp/tianyan/repair-result.json | jq '.repaired // 0')
|
||
echo "fix_count=$FIX_COUNT" >> $GITHUB_OUTPUT
|
||
echo "overall_risk=low" >> $GITHUB_OUTPUT
|
||
else
|
||
echo "pr_needed=false" >> $GITHUB_OUTPUT
|
||
echo "fix_count=0" >> $GITHUB_OUTPUT
|
||
fi
|
||
|
||
- name: "📝 生成 PR 摘要"
|
||
id: create_summary
|
||
if: steps.review.outputs.pr_needed == 'true'
|
||
run: |
|
||
DATE=$(date +%Y-%m-%d)
|
||
FIX_COUNT="${{ steps.review.outputs.fix_count }}"
|
||
RISK="${{ steps.review.outputs.overall_risk }}"
|
||
|
||
SUMMARY=$(cat <<'SUMMARY_EOF'
|
||
## 🌙 天眼夜间自动修复引擎 v3.0
|
||
|
||
**扫描时间:** SCAN_DATE
|
||
**修复数量:** FIX_NUM 个
|
||
**风险等级:** RISK_LEVEL
|
||
**天眼审核:** ✅ H1-H9 全部通过
|
||
|
||
### 天眼审核结果
|
||
|
||
- [x] H1 Secrets安全
|
||
- [x] H2 文件完整性
|
||
- [x] H3 依赖安全
|
||
- [x] H4 权限最小化
|
||
- [x] H5 天眼自保护
|
||
- [x] H6 变更范围
|
||
- [x] H7 Secrets引用完整性
|
||
- [x] H8 注释规范
|
||
- [x] H9 无残留调试代码
|
||
|
||
---
|
||
> 🤖 此 PR 由铸渊夜间修复引擎自动创建
|
||
> 📋 指令编号: ZY-TIANYAN-AUTOFIX-2026-0324-001
|
||
> ⏰ 请冰朔在晨间审阅后合并
|
||
SUMMARY_EOF
|
||
)
|
||
|
||
SUMMARY="${SUMMARY//SCAN_DATE/$DATE}"
|
||
SUMMARY="${SUMMARY//FIX_NUM/$FIX_COUNT}"
|
||
SUMMARY="${SUMMARY//RISK_LEVEL/$RISK}"
|
||
|
||
echo "summary<<SUMMARY_DELIM" >> $GITHUB_OUTPUT
|
||
echo "$SUMMARY" >> $GITHUB_OUTPUT
|
||
echo "SUMMARY_DELIM" >> $GITHUB_OUTPUT
|
||
|
||
- name: "🔄 Phase D 重试(如审核未通过)"
|
||
if: steps.review.outputs.pr_needed == 'false' && steps.repair.outputs.branch_name != ''
|
||
env:
|
||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||
run: |
|
||
echo "[天眼 v3.0] 审核未通过 — 进入重试流程"
|
||
RETRY=0
|
||
MAX_RETRY=2
|
||
|
||
while [ $RETRY -lt $MAX_RETRY ]; do
|
||
RETRY=$((RETRY + 1))
|
||
echo "🔄 重试第 $RETRY 轮..."
|
||
|
||
# Re-run repair with retry context
|
||
node .github/scripts/tianyan-repair.js
|
||
node .github/scripts/tianyan-review.js
|
||
|
||
PR_NEEDED=$(cat /tmp/tianyan/review-result.json | jq -r '.pr_needed // false')
|
||
if [ "$PR_NEEDED" = "true" ]; then
|
||
echo "✅ 重试第 $RETRY 轮通过"
|
||
break
|
||
fi
|
||
done
|
||
|
||
if [ "$PR_NEEDED" != "true" ]; then
|
||
echo "❌ $MAX_RETRY 轮重试后仍未通过 — 放弃自动修复"
|
||
# Clean up repair branch
|
||
git checkout main
|
||
git branch -D "${{ steps.repair.outputs.branch_name }}" 2>/dev/null || true
|
||
git push origin --delete "${{ steps.repair.outputs.branch_name }}" 2>/dev/null || true
|
||
fi
|
||
|
||
# ========================================
|
||
# Phase E+F: 创建 PR + 更新仪表盘
|
||
# ========================================
|
||
create-pr-and-dashboard:
|
||
needs: analyze-and-repair
|
||
if: needs.analyze-and-repair.outputs.pr_needed == 'true'
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
ref: main
|
||
|
||
- name: "📦 Phase E — 创建 Pull Request"
|
||
id: create_pr
|
||
env:
|
||
GH_TOKEN: ${{ secrets.DEPLOY_GITHUB_TOKEN }}
|
||
BRANCH: ${{ needs.analyze-and-repair.outputs.repair_branch }}
|
||
SUMMARY: ${{ needs.analyze-and-repair.outputs.repair_summary }}
|
||
FIX_COUNT: ${{ needs.analyze-and-repair.outputs.fix_count }}
|
||
RISK: ${{ needs.analyze-and-repair.outputs.overall_risk }}
|
||
run: |
|
||
echo "[天眼 v3.0] Phase E — 创建 Pull Request"
|
||
|
||
PR_TITLE="🌙 [AUTO-FIX] $(date +%Y-%m-%d) 天眼夜间自动修复 (${FIX_COUNT}个修复)"
|
||
|
||
# Create labels if they don't exist
|
||
gh label create "auto-fix" --color "0E8A16" --description "自动修复 PR" 2>/dev/null || true
|
||
gh label create "tianyan-v3" --color "1D76DB" --description "天眼 v3.0 引擎" 2>/dev/null || true
|
||
|
||
PR_URL=$(gh pr create \
|
||
--base main \
|
||
--head "$BRANCH" \
|
||
--title "$PR_TITLE" \
|
||
--body "$SUMMARY" \
|
||
--label "auto-fix,tianyan-v3" 2>/dev/null || echo "")
|
||
|
||
if [ -n "$PR_URL" ]; then
|
||
echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT
|
||
echo "✅ PR 已创建: $PR_URL"
|
||
|
||
# Add risk label
|
||
if [ "$RISK" = "low" ]; then
|
||
gh label create "safe-to-merge" --color "0E8A16" 2>/dev/null || true
|
||
gh pr edit "$PR_URL" --add-label "safe-to-merge" 2>/dev/null || true
|
||
elif [ "$RISK" = "medium" ]; then
|
||
gh label create "review-carefully" --color "FBCA04" 2>/dev/null || true
|
||
gh pr edit "$PR_URL" --add-label "review-carefully" 2>/dev/null || true
|
||
fi
|
||
else
|
||
echo "⚠️ PR 创建失败"
|
||
echo "pr_url=" >> $GITHUB_OUTPUT
|
||
fi
|
||
|
||
- name: "📊 Phase F — 更新仪表盘"
|
||
env:
|
||
GH_TOKEN: ${{ secrets.DEPLOY_GITHUB_TOKEN }}
|
||
PR_URL: ${{ steps.create_pr.outputs.pr_url }}
|
||
FIX_COUNT: ${{ needs.analyze-and-repair.outputs.fix_count }}
|
||
run: |
|
||
echo "[天眼 v3.0] Phase F — 更新仪表盘"
|
||
DATE=$(date +%Y-%m-%d)
|
||
TIME=$(date +%H:%M)
|
||
|
||
# Update DASHBOARD.md status table
|
||
if [ -f DASHBOARD.md ]; then
|
||
sed -i "s/| 上次扫描 | .*/| 上次扫描 | $DATE $TIME CST |/" DASHBOARD.md
|
||
sed -i "s/| 扫描结果 | .*/| 扫描结果 | ⚠️ 发现错误,已自动修复 |/" DASHBOARD.md
|
||
sed -i "s/| 自动修复 | .*/| 自动修复 | ✅ ${FIX_COUNT} 个已修复 |/" DASHBOARD.md
|
||
|
||
if [ -n "$PR_URL" ]; then
|
||
PR_NUM=$(echo "$PR_URL" | grep -oP '\d+$' || echo "?")
|
||
sed -i "s/| 待合并 PR | .*/| 待合并 PR | 🟡 PR #${PR_NUM} |/" DASHBOARD.md
|
||
fi
|
||
|
||
git config user.name "tianyan-bot"
|
||
git config user.email "tianyan@guanghulab.com"
|
||
git add DASHBOARD.md
|
||
git diff --cached --quiet || git commit -m "[DASHBOARD] 更新天眼仪表盘 $DATE — ${FIX_COUNT}个修复待合并"
|
||
git push origin main || true
|
||
fi
|
||
|
||
# ========================================
|
||
# 无错误时的仪表盘更新
|
||
# ========================================
|
||
dashboard-no-errors:
|
||
needs: nightly-scan
|
||
if: needs.nightly-scan.outputs.has_errors == 'false'
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: "✅ 更新仪表盘 — 今日无异常"
|
||
env:
|
||
GH_TOKEN: ${{ secrets.DEPLOY_GITHUB_TOKEN }}
|
||
run: |
|
||
DATE=$(date +%Y-%m-%d)
|
||
TIME=$(date +%H:%M)
|
||
echo "[天眼 v3.0] 今日无异常,更新仪表盘"
|
||
|
||
if [ -f DASHBOARD.md ]; then
|
||
sed -i "s/| 上次扫描 | .*/| 上次扫描 | $DATE $TIME CST |/" DASHBOARD.md
|
||
sed -i "s/| 扫描结果 | .*/| 扫描结果 | ✅ 今日无异常 |/" DASHBOARD.md
|
||
sed -i "s/| 自动修复 | .*/| 自动修复 | — |/" DASHBOARD.md
|
||
sed -i "s/| 需人工处理 | .*/| 需人工处理 | — |/" DASHBOARD.md
|
||
|
||
git config user.name "tianyan-bot"
|
||
git config user.email "tianyan@guanghulab.com"
|
||
git add DASHBOARD.md
|
||
git diff --cached --quiet || git commit -m "[DASHBOARD] 今日无异常 $DATE"
|
||
git push origin main || true
|
||
fi
|