10—把 SkillSentry 接入 CI:每次改动都有质量门禁

总:这篇讲什么

CI 集成分两层:

  1. 触发层:什么时候运行测评(SKILL.md 变更时)
  2. 门禁层:测评结果如何影响 PR 合并(FAIL 阻止合并)

只做触发层而不做门禁层,测评是装饰品。只有两层都到位,才真正建立质量门禁。

本篇给出:

  • GitHub Actions 完整 workflow 配置
  • exit code 设计与 CI 状态映射
  • branch protection 配置
  • 历史趋势数据的积累方式

一、为什么 CI 里可以用 --dangerously-skip-permissions

这是很多人看到这个 flag 时的第一反应:「这个名字太危险了,能用吗?」

先说结论:在 CI 环境里,这个 flag 是安全的,应该用;在本地不应该用。

原因在于权限模型的不同

环境 人工审批的意义 skip-permissions 的影响
本地(开发者机器) 开发者可以在测评过程中选择允许或拒绝某些操作 跳过后,风险操作无人审核
CI 容器 无人值守,任何权限弹窗都会卡死流程 在隔离容器中跳过是正常设计

CI 容器是一次性的隔离环境:

  • 每次 workflow 运行都是全新容器
  • 容器内没有持久状态
  • 容器内的操作不影响真实生产环境
  • 运行完即销毁

所以 --dangerously-skip-permissions 在 CI 里的含义是:「在这个一次性容器里,跳过权限确认,让测评全程无需人工干预」。危险的是「绕过生产系统的权限控制」,而不是「跳过 AI 工具调用确认」。


二、GitHub Actions 完整配置

基础配置:SKILL.md 变更时触发

yaml 复制代码
# .github/workflows/skill-eval.yml
name: SkillSentry Evaluation

on:
  pull_request:
    paths:
      - 'skills/*/SKILL.md'   # 任意 Skill 的 SKILL.md 变更时触发
  push:
    branches: [main]
    paths:
      - 'skills/*/SKILL.md'

jobs:
  skill-eval:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 2   # 需要对比前一个版本

      - name: Detect changed skills
        id: changed-skills
        run: |
          CHANGED=$(git diff --name-only HEAD~1 HEAD -- 'skills/*/SKILL.md' | \
            sed 's|skills/||' | sed 's|/SKILL.md||' | tr '\n' ',')
          echo "skills=${CHANGED}" >> $GITHUB_OUTPUT
          echo "Changed skills: ${CHANGED}"

      - name: Install Claude Code
        run: |
          npm install -g @anthropic-ai/claude-code
          # 验证安装
          claude --version

      - name: Run SkillSentry evaluation
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          # 对每个变更的 Skill 运行 smoke 测评
          IFS=',' read -ra SKILLS <<< "${{ steps.changed-skills.outputs.skills }}"
          OVERALL_EXIT=0

          for SKILL in "${SKILLS[@]}"; do
            if [ -z "$SKILL" ]; then continue; fi
            echo "=== Evaluating: $SKILL ==="

            claude --dangerously-skip-permissions \
              -p "smoke 测评 ${SKILL} 自动" \
              --output-format stream-json | tee "eval-${SKILL}.log"

            EXIT_CODE=${PIPESTATUS[0]}
            if [ $EXIT_CODE -ne 0 ]; then
              echo "::error::Skill ${SKILL} evaluation FAILED (exit code: ${EXIT_CODE})"
              OVERALL_EXIT=1
            fi
          done

          exit $OVERALL_EXIT

      - name: Upload evaluation reports
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: skill-eval-reports-${{ github.run_id }}
          path: |
            **/*.eval-report.html
            **/sessions/**/*.html
          retention-days: 30

完整门禁配置:standard 测评 + 发布等级检查

yaml 复制代码
# .github/workflows/skill-eval-full.yml
name: SkillSentry Full Evaluation (Pre-release)

on:
  push:
    branches: [release/*]
    paths:
      - 'skills/*/SKILL.md'

jobs:
  detect-skills:
    runs-on: ubuntu-latest
    outputs:
      skills: ${{ steps.changed-skills.outputs.skills }}
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 2

      - name: Detect changed skills
        id: changed-skills
        run: |
          SKILLS=$(git diff --name-only HEAD~1 HEAD -- 'skills/*/SKILL.md' | \
            sed 's|skills/||' | sed 's|/SKILL.md||' | jq -R -s -c 'split("\n")[:-1]')
          echo "skills=${SKILLS}" >> "$GITHUB_OUTPUT"
          echo "Changed skills: ${SKILLS}"

  skill-eval-standard:
    needs: detect-skills
    if: needs.detect-skills.outputs.skills != '[]'
    runs-on: ubuntu-latest
    timeout-minutes: 60

    strategy:
      matrix:
        skill: ${{ fromJson(needs.detect-skills.outputs.skills) }}
      fail-fast: false   # 一个 Skill FAIL 不影响其他 Skill 的测评继续

    steps:
      - uses: actions/checkout@v6

      - name: Install Claude Code
        run: |
          npm install -g @anthropic-ai/claude-code
          claude --version

      - name: Run standard evaluation
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          claude --dangerously-skip-permissions \
            -p "standard 测评 ${{ matrix.skill }} 自动" \
            --output-format stream-json

      - name: Check release grade
        run: |
          # 从报告提取发布等级
          GRADE=$(find "sessions/${{ matrix.skill }}" -name "*.json" -print0 | \
            xargs -0 -r grep -h -o '"release_grade":"[A-Z]*"' | \
            tail -1 | sed 's/.*:"//;s/"$//' || true)

          echo "Release grade: ${GRADE}"
          echo "GRADE=${GRADE}" >> "$GITHUB_ENV"

          case "$GRADE" in
            "S"|"A"|"B"|"C")
              echo "Grade ${GRADE}: PASS"
              ;;
            "D"|"F"|"FAIL"|"")
              echo "::error::Skill ${{ matrix.skill }} did not pass (grade: ${GRADE})"
              exit 1
              ;;
            *)
              echo "::error::Unknown grade for ${{ matrix.skill }}: ${GRADE}"
              exit 1
              ;;
          esac

      - name: Save trend data
        if: always()
        run: |
          # 记录历史趋势数据到 gh-pages 或 artifact
          DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
          echo "{\"date\":\"${DATE}\",\"skill\":\"${{ matrix.skill }}\",\"grade\":\"${GRADE}\",\"run\":${{ github.run_number }}}" \
            >> trend-data.jsonl

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: eval-${{ matrix.skill }}-${{ github.run_id }}
          path: |
            sessions/${{ matrix.skill }}/**/*.html
            trend-data.jsonl
          retention-days: 90

三、exit code 设计

CI 系统通过 exit code 判断步骤是否成功。SkillSentry 的 exit code 规范:

exit code 含义 CI 步骤状态
0 测评通过(S/A/B 级) ✅ success
1 测评 FAIL ❌ failure
2 环境错误(MCP 不可用、Skill 找不到) ❌ failure
3 测评结果不稳定(quick 两次差距 > 15%) 默认是 failure;如果团队希望只标 warning,必须显式捕获后转成 ::warning::

关键设计:GitHub Actions 本身只认 0 / 非 0。exit code 1(测评 FAIL)和 exit code 2(环境错误)必须阻止 PR 合并;exit code 3 如果被定义为「不稳定但不阻断」,就不能直接把 3 作为步骤退出码返回,而要在脚本里捕获后输出 warning,再由团队策略决定是否放行。

--output-format stream-json--output-format json 的用途不同:

格式 适合场景 用法
stream-json 长时间运行的 CI 步骤 边执行边输出日志,适合配合 tee 保存执行过程
json 后续脚本需要解析最终结果 输出完整 JSON 到文件,适合用 jq 读取判定字段

前面的 workflow 用 stream-json 是为了让 CI 页面实时显示执行过程;下面的示例用 json 是为了演示如何从结构化结果里解析 pass/fail。

等待 exit code 的方式:

bash 复制代码
# 方式一:直接检查 claude 命令的退出码
claude --dangerously-skip-permissions -p "smoke 测评 my-skill 自动"
if [ $? -ne 0 ]; then
  echo "Evaluation failed"
  exit 1
fi

# 方式二:从输出的 JSON 中解析结果
claude --dangerously-skip-permissions \
  -p "smoke 测评 my-skill 自动" \
  --output-format json > result.json

PASS=$(jq -r '.result.pass' result.json)
if [ "$PASS" != "true" ]; then
  echo "Evaluation failed: $(jq -r '.result.reason' result.json)"
  exit 1
fi

如果要把「结果不稳定」标成 warning 而不是失败,需要显式处理:

bash 复制代码
set +e
claude --dangerously-skip-permissions -p "quick 测评 my-skill 自动"
EXIT_CODE=$?
set -e

case "$EXIT_CODE" in
  0)
    echo "Evaluation passed"
    ;;
  3)
    echo "::warning::Evaluation is unstable; mark as conditional pass and require follow-up"
    exit 0
    ;;
  *)
    echo "::error::Evaluation failed with exit code ${EXIT_CODE}"
    exit "$EXIT_CODE"
    ;;
esac

四、Branch Protection 配置

在 GitHub 仓库设置中配置 branch protection rule,让 CI 状态决定 PR 可合并性:

  1. 进入仓库 Settings → Branches → Add branch protection rule
  2. Branch name pattern:main(或你的主分支名)
  3. 勾选 Require status checks to pass before merging
  4. 在 Status checks 搜索框中添加:skill-eval / skill-eval
  5. 勾选 Require branches to be up to date before merging

配置完成后效果:

  • PR 中有 SKILL.md 变更 → 自动触发测评 → 测评 FAIL → PR 显示红色 × → 无法合并
  • 测评通过 → PR 显示绿色 ✓ → 允许合并

五、历史趋势数据

单次测评只能告诉你「现在通不过」,历史趋势能告诉你「质量在下降还是在提升」。

数据收集方案

每次 CI 运行将测评结果写入 trend JSONL 文件:

jsonl 复制代码
{"date":"2026-04-10T09:00:00Z","skill":"em-reimbursement-v3","grade":"A","pass_rate":0.92,"delta":0.67,"run":142}
{"date":"2026-04-11T14:30:00Z","skill":"em-reimbursement-v3","grade":"A","pass_rate":0.91,"delta":0.65,"run":145}
{"date":"2026-04-12T11:00:00Z","skill":"em-reimbursement-v3","grade":"B","pass_rate":0.83,"delta":0.52,"run":148}

趋势预警规则

在 CI 中加入趋势分析:

bash 复制代码
# 检查最近 5 次的趋势
TREND=$(tail -5 trend-data.jsonl | jq -s '[.[].pass_rate] | (last - first)')

if (( $(echo "$TREND < -0.1" | bc -l) )); then
  echo "::warning::Pass rate has dropped by more than 10% in last 5 runs"
fi

报告持久化

将 HTML 报告上传到 GitHub Actions artifacts,保留 90 天:

yaml 复制代码
- uses: actions/upload-artifact@v4
  with:
    name: skill-report-${{ github.run_number }}
    path: sessions/**/*.html
    retention-days: 90

配合 GitHub Pages,可以将趋势数据可视化成折线图,在团队 wiki 中直接查看质量趋势。


六、常见 CI 问题与处理

问题 原因 处理
CI 卡死不退出 测评等待用户确认(忘记加 自动 在 prompt 末尾加 自动 参数
exit code 总是 0 即使测评 FAIL claude 命令未正确传递测评结果 改用 --output-format json 解析结果字段
MCP 工具在 CI 容器不可用 CI 容器没有配置 MCP Server 确认 MCP Server 可通过网络访问,或配置为 text_generation 降级
Token 消耗超出预算 standard/full 模式在 CI 中太贵 PR 评审用 smoke,release 分支用 standard
并发 Skill 测评相互干扰 会话目录冲突 确保每个 Skill 使用独立 session 目录
API key 在 PR 中无法访问 fork 的 PR 无法读取 secrets 使用 pull_request_target 事件,或为 fork PR 单独配置

七、分级门禁策略

不是所有 SKILL.md 改动都需要同等严格的测评。推荐分级策略:

触发条件 测评模式 阻断 PR? 典型场景
任意 PR + SKILL.md 变更 smoke ✅ FAIL 阻断 日常开发迭代
release 分支 + SKILL.md 变更 standard ✅ FAIL 阻断 提测前验收
main 分支合并 + S/A 级 Skill full ✅ FAIL 阻断 正式发布前
定时任务(每天凌晨) quick ❌ 不阻断 环境健康检查

定时健康检查可以发现「Skill 没改但环境变了导致测评失败」的情况,比如 MCP Server 升级导致工具调用行为变化。

yaml 复制代码
# 定时健康检查
on:
  schedule:
    - cron: '0 2 * * *'   # 每天凌晨 2 点(UTC)

jobs:
  health-check:
    # ... 对所有已注册 Skill 运行 quick 测评
    # FAIL 不阻断,但通过 Slack/飞书通知

八、工程落地建议:CI 分成两条线

前面的 workflow 是门禁骨架。真正落地时,建议让 CI Runner 以结构化测评结果为准,读取 grading-summary.json / session.json / authoritative_pass_rate,再返回明确的 exit code:

推荐脚本分工

  • sentry_ci.py:核心判决脚本,读取 grading-summary.json / session.json / authoritative_pass_rate → PASS/FAIL 判决 → exit code
  • update_history.py:追加 history.json 历史记录,支持趋势分析和回归检测
  • report_to_checks.py:将结果推送为 GitHub Checks 注解,PR 页面直接可见

相比前面的 YAML 骨架,结构化 Runner 的改进

本文描述 当前实现
grep CLI 输出判 PASS/FAIL grading-summary.json / session.json 结构化数据,不依赖 CLI 输出格式
硬编码阈值 --threshold 参数化(默认 0.80)
无历史对比 history.json + Δ < 0 自动 FAIL
单 Skill matrix 自动检测变更的 SKILL.md 并行测评
无 GitHub Checks 集成 report_to_checks.py 推送注解

CI 建议分成两条线,不要混成一条 workflow:

  1. 业务 Skill 质量门禁skill-eval.yml,用于评估被测 Skill 的 smoke / standard / full 结果,核心仍是让 FAIL 阻断合并。
  2. SkillSentry 本体自检skillsentry-self-test.yml,用于验证 SkillSentry 自身脚本、契约、模板和工作流没有被改坏。

本体自检的推荐入口是确定性脚本,而不是再启动一次真实 LLM 测评:

bash 复制代码
python scripts/verify_deterministic.py --format json

手动触发深度自检时再打开 full:

bash 复制代码
python scripts/verify_deterministic.py --full --format json

GitHub Actions 示例应使用当前 Node 运行时兼容的 action 版本:

yaml 复制代码
steps:
  - uses: actions/checkout@v6
  - uses: actions/setup-python@v6
    with:
      python-version: '3.11'

这类工程落地常见坑是:workflow 模板已经升级到新 major,但仓库里的实际 workflow 还停在旧 major,容易触发运行时退役提醒或形成模板与线上不一致。后续 workflow 模板和仓库内实际 workflow 要保持一致,并用 verify_workflow_action_versions.py 扫描旧 major,避免模板修了、线上 workflow 没修。

另一个容易忽略的点是 GitHub 权限。推送 .github/workflows/*.yml 不只需要普通 repo 权限,还需要 token 带 workflow scope;否则代码改好了也会被远端拒绝。


九、实战补充:最小 CI Gate

如果团队暂时跑不起 full 测评,至少应该先做一个最小静态门禁:

text 复制代码
PR 提交
  -> 自动静态检查
  -> 检查准入标准
    -> PASS:允许进入后续流程
    -> FAIL:阻断 + 通知 + 修复建议

最小准入标准可以先这样设:

维度 阈值 理由
L1 description >= 3/5 不能缺核心字段
L2 HiL 风险 >= 3/5 不能没有 HiL 节点
L3 复杂度 <= 25 复杂度不能失控
总分 >= 15/25 B 级以上才进入后续流程

最小 GitHub Actions 结构:

yaml 复制代码
name: Skill Static Gate

on:
  pull_request:
    paths:
      - "skills/*/SKILL.md"

jobs:
  static-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - name: Run static check
        run: |
          for skill_dir in skills/*/; do
            skill_name=$(basename "$skill_dir")
            python tools/sentry-static.py "$skill_dir/SKILL.md" \
              --format json > "static-${skill_name}.json"
          done

CI 失败时,通知里不要只写"失败",要给出:

text 复制代码
Skill 名称
总分
失败维度
修复建议
报告链接

这个最小 Gate 不替代完整测评,也不替代人工发布判断。它的价值是把低质量 Skill 挡在 review 之前。


总结

把 SkillSentry 接入 CI 的三件事:

  1. 触发paths: skills/*/SKILL.mdSKILL.md 变更时自动运行
  2. 阻断:exit code 1/2 → CI 失败 → branch protection 阻止合并
  3. 积累:每次结果写 trend JSONL → 质量趋势可见

关键原则:测评不阻断发布 = 没有门禁。只有 FAIL 真的能阻止 PR 合并,质量保证才是真实的,而不是报表上的数字。

工具在 GitHub:github.com/xingzaidadi...

相关推荐
编程探索者小陈18 小时前
接口自动化测试(一)
python·测试
1candobetter1 天前
单接口性能测试实践总结:压测方案设计、成功判定与 JVM 监控分析
java·jvm·压力测试·测试
学代码的真由酱2 天前
Java多用户一对一网页聊天室-测试报告
java·开发语言·功能测试·测试
小当家1054 天前
如何评估你的 Skill 质量——从触发准确率到输出质量的系统方法
测试
编程探索者小陈6 天前
【测试】之测试分类篇
测试
kida_yuan7 天前
【以太来袭】7. Besu 性能基线(Caliper)
区块链·测试
qq_白羊座10 天前
测试资产复用维护方法
测试·测试资产
HuskyYellow12 天前
第 1 篇:没有专职测试的小团队,为什么需要 ai-phone?
人工智能·开源·测试
康谋自动驾驶12 天前
智驾仿真测试团队必看:ADAS HiL测试引入3DGS的ROI测算与结论!
自动驾驶·测试·3dgs·hil测试·场景生成·智驾仿真