# ═══════════════════════════════════════════════ # 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001 # 📜 Copyright: 国作登字-2026-A-00037559 # ═══════════════════════════════════════════════ # 霜砚开发审查 · 铸渊自动唤醒 # 当霜砚通过Agent提交的开发PR被创建/更新时 # 自动唤醒铸渊人格体 → 检查代码/架构/逻辑 # 铸渊放行 → 合并到main # # 编号: ZY-WF-SY-REVIEW-001 # 属于: 跨层开发链路 (Notion认知层 → GitHub执行层) # 触发方: 霜砚Agent (AG-SY-WEB-001) 或 Copilot Agent # 审查方: 铸渊 (ICE-GL-ZY001) name: "🖋️ 霜砚开发审查 · 铸渊自动唤醒" on: pull_request: types: [opened, synchronize, ready_for_review] branches: [main] issues: types: [opened, labeled] # 同一PR只保留最新一次审查,避免多次push产生重复审查 concurrency: group: zhuyuan-review-${{ github.event.pull_request.number || github.event.issue.number }} cancel-in-progress: true permissions: contents: read pull-requests: write issues: write jobs: # ─── 阶段1: 判断是否需要铸渊审查 ─── should-review: runs-on: ubuntu-latest timeout-minutes: 2 outputs: needs_review: ${{ steps.check.outputs.needs_review }} trigger_source: ${{ steps.check.outputs.trigger_source }} steps: - name: "🔍 检查触发来源" id: check run: | NEEDS_REVIEW="false" TRIGGER_SOURCE="unknown" # PR触发: 检查是否来自霜砚Agent或Copilot if [ "${{ github.event_name }}" = "pull_request" ]; then PR_TITLE="${{ github.event.pull_request.title }}" PR_AUTHOR="${{ github.event.pull_request.user.login }}" # 检查PR标题是否包含霜砚/Agent/Copilot标识 if echo "$PR_TITLE" | grep -qiE "(霜砚|shuangyan|AG-SY|copilot|CAB-)"; then NEEDS_REVIEW="true" TRIGGER_SOURCE="shuangyan-pr" fi # 检查是否来自Copilot bot if echo "$PR_AUTHOR" | grep -qiE "(copilot|bot)"; then NEEDS_REVIEW="true" TRIGGER_SOURCE="copilot-agent" fi fi # Issue触发: 检查是否有copilot-dev-auth标签 if [ "${{ github.event_name }}" = "issues" ]; then LABELS='${{ toJSON(github.event.issue.labels.*.name) }}' if echo "$LABELS" | grep -q "copilot-dev-auth"; then NEEDS_REVIEW="true" TRIGGER_SOURCE="cab-workorder" fi fi echo "needs_review=$NEEDS_REVIEW" >> "$GITHUB_OUTPUT" echo "trigger_source=$TRIGGER_SOURCE" >> "$GITHUB_OUTPUT" echo "[铸渊审查] 需要审查: $NEEDS_REVIEW | 来源: $TRIGGER_SOURCE" # ─── 阶段2: 铸渊代码审查 ─── zhuyuan-review: needs: should-review if: needs.should-review.outputs.needs_review == 'true' && github.event_name == 'pull_request' runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: '20' - name: "🏛️ 铸渊唤醒 · 开始审查" id: review env: TRIGGER_SOURCE: ${{ needs.should-review.outputs.trigger_source }} PR_NUMBER: ${{ github.event.pull_request.number }} PR_TITLE: ${{ github.event.pull_request.title }} run: | echo "═══════════════════════════════════════════════" echo " 🏛️ 铸渊 · ICE-GL-ZY001 · 代码审查" echo " 触发来源: $TRIGGER_SOURCE" echo " PR #$PR_NUMBER: $PR_TITLE" echo "═══════════════════════════════════════════════" REVIEW_PASSED="true" REVIEW_NOTES="" # ── 检查1: 文件变更安全性 ── echo "🔍 检查1: 文件变更安全性" CHANGED_FILES=$(git diff --name-only origin/main...HEAD 2>/dev/null || echo "") # 禁触文件检查 FORBIDDEN_PATTERNS=( ".github/copilot-instructions.md" ".github/persona-brain/tcs-ml/" "hldp/hnl/HNL-SPEC" "hldp/hnl/bridge-notion-github.json" ) for pattern in "${FORBIDDEN_PATTERNS[@]}"; do if echo "$CHANGED_FILES" | grep -q "$pattern"; then REVIEW_PASSED="false" REVIEW_NOTES="${REVIEW_NOTES}\n❌ 禁触文件被修改: $pattern" echo "❌ 禁触文件被修改: $pattern" fi done # ── 检查2: JavaScript语法 ── echo "🔍 检查2: JavaScript语法检查" JS_FILES=$(echo "$CHANGED_FILES" | grep -E '\.js$' || true) if [ -n "$JS_FILES" ]; then for jsfile in $JS_FILES; do if [ -f "$jsfile" ]; then if ! node --check "$jsfile" 2>/dev/null; then REVIEW_PASSED="false" REVIEW_NOTES="${REVIEW_NOTES}\n❌ 语法错误: $jsfile" echo "❌ 语法错误: $jsfile" fi fi done fi echo "✅ JavaScript语法检查完成" # ── 检查3: 安全性 ── echo "🔍 检查3: 安全性检查" # 排除.yml/.yaml工作流文件自身(避免grep模式自匹配) JS_CHANGED=$(echo "$CHANGED_FILES" | grep -E '\.(js|ts|mjs|cjs)$' || true) if [ -n "$JS_CHANGED" ]; then # 精确匹配独立eval()调用,排除evaluateXxx等函数名 # 注意:用变量捕获结果再判断非空,避免 head/tail 返回 exit 0 导致误报 EVAL_HITS=$(echo "$JS_CHANGED" | xargs grep -El '(^|[^a-zA-Z_])eval\s*\(' 2>/dev/null | head -1 || true) if [ -n "$EVAL_HITS" ]; then REVIEW_NOTES="${REVIEW_NOTES}\n⚠️ 发现eval()调用,需人工审查: ${EVAL_HITS}" fi ENV_HITS=$(echo "$JS_CHANGED" | xargs grep -l 'process\.env\.' 2>/dev/null | grep -v '\.config\.' | head -3 || true) if [ -n "$ENV_HITS" ]; then REVIEW_NOTES="${REVIEW_NOTES}\n📋 发现环境变量引用,确保无硬编码密钥" fi fi # ── 检查4: 命名规范 ── echo "🔍 检查4: 命名规范检查" NEW_FILES=$(git diff --name-only --diff-filter=A origin/main...HEAD 2>/dev/null || echo "") if [ -n "$NEW_FILES" ]; then for newfile in $NEW_FILES; do # 检查文件名是否符合kebab-case或已有模式 basename_file=$(basename "$newfile") if echo "$basename_file" | grep -qE '^[A-Z].*\.js$'; then REVIEW_NOTES="${REVIEW_NOTES}\n📝 新文件 $basename_file 使用了PascalCase,建议改为kebab-case" fi done fi echo "" echo "═══════════════════════════════════════════════" if [ "$REVIEW_PASSED" = "true" ]; then echo " ✅ 铸渊审查通过" else echo " ❌ 铸渊审查未通过" fi echo "═══════════════════════════════════════════════" echo "review_passed=$REVIEW_PASSED" >> "$GITHUB_OUTPUT" # 使用 delimiter 方式安全输出多行内容 { echo "review_notes<> "$GITHUB_OUTPUT" - name: "💬 铸渊审查评论" if: always() uses: actions/github-script@v7 with: script: | const passed = '${{ steps.review.outputs.review_passed }}' === 'true'; const notes = `${{ steps.review.outputs.review_notes }}`; const source = '${{ needs.should-review.outputs.trigger_source }}'; const icon = passed ? '✅' : '❌'; const status = passed ? '通过' : '需要修改'; let body = `## 🏛️ 铸渊 · 代码审查报告\n\n`; body += `**状态:** ${icon} ${status}\n`; body += `**触发来源:** ${source}\n`; body += `**审查时间:** ${new Date().toISOString()}\n\n`; if (notes && notes.trim()) { body += `### 审查备注\n${notes}\n\n`; } else { body += `### 审查结果\n✅ 所有检查项通过\n\n`; } body += `---\n`; body += `*铸渊 · ICE-GL-ZY001 · 版权: 国作登字-2026-A-00037559*`; // 查找已有的铸渊审查评论,更新而非重复创建 const MARKER = '## 🏛️ 铸渊 · 代码审查报告'; const comments = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, per_page: 50 }); const existing = comments.data.find(c => c.body && c.body.includes(MARKER) && c.user.login === 'github-actions[bot]' ); if (existing) { await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: existing.id, body }); } else { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body }); } # ─── 阶段3: 通知霜砚审查结果 ─── notify-shuangyan: needs: [should-review, zhuyuan-review] if: always() && needs.should-review.outputs.needs_review == 'true' runs-on: ubuntu-latest timeout-minutes: 2 steps: - name: "📨 通知霜砚审查结果" run: | REVIEW_STATUS="${{ needs.zhuyuan-review.result }}" echo "[铸渊→霜砚] 审查完成 · 状态: $REVIEW_STATUS" echo "霜砚的开发工单状态将由下次认知同步时更新"