SkillSentry持续集成接入完整指南:每次代码变动自动触发质量门禁
摘要
CI流水线分为触发层与门禁层:触发层监测SKILL md变更自动运行测评,门禁层依据FAIL结果阻
CI 集成分两层:触发层决定何时启动测评(SKILL.md 变更时),门禁层决定测评结果如何控制 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 变更时触发
# .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 测评 + 发布等级检查
# .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: Sa ve 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 的方式:
# 方式一:直接检查 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 而非 failure,需显式处理:
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 能否合并:
- 进入仓库 Settings → Branches → Add branch protection rule
- Branch name pattern:
main(或你的主分支名) - 勾选 Require status checks to pass before merging
- 在 Status checks 搜索框中添加:
skill-eval / skill-eval - 勾选 Require branches to be up to date before merging
配置完成后效果:PR 中有 SKILL.md 变更 → 自动触发测评 → 测评 FAIL → PR 显示红色 × → 无法合并;测评通过 → PR 显示绿色 ✓ → 允许合并。
五、历史趋势数据
单次测评只能告诉你“当前通不过”,历史趋势能告诉你“质量在下降还是提升”。
数据收集方案
每次 CI 运行将测评结果写入 trend 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 中加入趋势分析:
# 检查最近 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 天:
- 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 升级引发工具调用行为变化。
# 定时健康检查
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 codeupdate_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:
- 业务 Skill 质量门禁:
skill-eval.yml,用于评估被测 Skill 的 smoke / standard / full 结果,核心仍是让 FAIL 阻断合并。 - SkillSentry 本体自检:
skillsentry-self-test.yml,用于验证 SkillSentry 自身脚本、契约、模板和工作流未被改坏。
本体自检的推荐入口是确定性脚本,而非再次启动真实 LLM 测评:
python scripts/verify_deterministic.py --format json
手动触发深度自检时再打开 full:
python scripts/verify_deterministic.py --full --format json
GitHub Actions 示例应使用当前 Node 运行时兼容的 action 版本:
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 测评,至少应先做一个最小静态门禁:
PR 提交 -> 自动静态检查 -> 检查准入标准 -> PASS:允许进入后续流程 -> FAIL:阻断 + 通知 + 修复建议
最小准入标准可先设如下:
| 维度 | 阈值 | 理由 |
|---|---|---|
| L1 description | >= 3/5 | 不能缺核心字段 |
| L2 HiL 风险 | >= 3/5 | 不能没有 HiL 节点 |
| L3 复杂度 | <= 25 | 复杂度不能失控 |
| 总分 | >= 15/25 | B 级以上才进入后续流程 |
最小 GitHub Actions 结构:
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 失败时,通知里不要只写“失败”,要给出:
Skill 名称 | 总分 | 失败维度 | 修复建议 | 报告链接
这个最小 Gate 不替代完整测评,也不替代人工发布判断。它的价值是把低质量 Skill 挡在 review 之前。
总结
将 SkillSentry 接入 CI 的核心就是三件事:
- 触发:
paths: skills/*/SKILL.md→ SKILL.md 变更时自动运行 - 阻断:exit code 1/2 → CI 失败 → branch protection 阻止合并
- 积累:每次结果写入 trend JSONL → 质量趋势可见
关键原则:测评不阻断发布 = 没有门禁。只有 FAIL 真能阻止 PR 合并,质量保证才是真实的,而非报表上的数字。
来源:互联网
本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。