-
Notifications
You must be signed in to change notification settings - Fork 0
123 lines (108 loc) · 4.79 KB
/
Copy pathpr.yml
File metadata and controls
123 lines (108 loc) · 4.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
name: PR
# Runs on every pull request. Four jobs, three phases:
# 1. fork-gate — always runs. Fails fast on PRs opened from a fork so we
# never spend CI minutes on (or grant any token to) untrusted code.
# 2. lint-tests — formats, lints, and unit-tests the tooling that produces
# skills (scripts/, tests/, pyproject.toml). Runs after fork-gate: if the
# generator is broken, validating skill output is meaningless.
# 3. skills — validates the SKILL.md profiles the PR changed. Gated on
# lint-tests passing.
# 4. results — always runs (`if: always()`), needs every other job, and
# fails if any of them failed or was cancelled. This is the SINGLE
# required status check on `main`, so we can add or rename jobs in this
# file without touching branch protection.
#
# Action pinning policy:
# * First-party actions owned by GitHub (actions/*) are pinned to a major
# version tag — GitHub guarantees those tags forward-only. Easier to read,
# Dependabot still bumps majors.
# * Third-party actions are pinned to an immutable commit SHA with the
# human-readable version in a trailing comment, so a tag hijack cannot
# silently swap the action's code under us. Dependabot bumps these too.
on:
pull_request:
# Least privilege: every job here only reads the repo.
permissions:
contents: read
# Cancel superseded runs on the same PR (a force-push or new commit makes the
# in-flight run's results stale). The group key is keyed on the PR's head
# ref so concurrent PRs don't cancel each other. Pushes to a branch outside a
# PR (this workflow doesn't trigger on `push`, but the guard is defensive)
# would fall back to ${{ github.ref }}.
concurrency:
group: pr-${{ github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
# 0. Hard gate: fail loudly on PRs opened from a fork. This produces a real
# failed status check (not a neutral "skipped") so branch protection can
# require it before merge. Maintainers re-land fork PRs from an in-repo
# branch after review.
fork-gate:
runs-on: ubuntu-24.04
steps:
- name: Reject PRs from forks
env:
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
BASE_REPO: ${{ github.repository }}
run: |
set -euo pipefail
if [ "$HEAD_REPO" != "$BASE_REPO" ]; then
echo "PR opened from fork ($HEAD_REPO); maintainers must re-land it from a branch in $BASE_REPO."
exit 1
fi
# 1. Validate the codebase that produces skills — runs before skill validation.
lint-tests:
needs: fork-gate
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # full history so we can diff against the base branch
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.11.19"
enable-cache: true
- name: Check Python formatting (2-space, double quotes)
run: uv run ruff format --check .
- name: Lint Python (style, imports, docstrings)
run: uv run ruff check .
- name: Run unit tests
run: uv run pytest -q
# 2. Validate the skill profiles the PR changed — only after codebase passes.
skills:
needs: lint-tests
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # full history so we can diff against the base branch
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.11.19"
enable-cache: true
- name: Validate changed skills
run: uv run python scripts/validate_skill.py --base "origin/${{ github.base_ref }}"
# 3. Aggregator. Always runs (even if upstream jobs failed or were cancelled)
# and fails if any required job failed or was cancelled. Branch protection
# only needs to require THIS one check — adding or renaming jobs above
# never requires updating branch protection again.
#
# Note: a `skipped` result is treated as a pass on purpose, so jobs we add
# later with an `if:` filter (e.g. "only on changes to X") don't false-fail
# the gate when they intentionally don't run.
results:
if: always()
needs: [fork-gate, lint-tests, skills]
runs-on: ubuntu-24.04
steps:
- name: Fail if any required job failed or was cancelled
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: |
set -euo pipefail
echo "At least one required job failed or was cancelled."
echo '${{ toJSON(needs) }}'
exit 1
- name: All required jobs passed
run: echo "All required jobs passed (or were intentionally skipped)."