Pipeline & Governance
How a change moves from a pull request into the live catalogue, and the controls that keep that path safe.
Lifecycle at a glance
open PR → fork-gate → lint-tests → skills → results → ✅ mergeable
│
merge to main │
▼
npx skills add bcgov/agent-skills
reads the new content directly
from the repo — no publish step,
no registry, no auth token.
PR workflow: .github/workflows/pr.yml
Runs on every pull request with least-privilege permissions (contents: read). Four jobs across three phases; the fourth aggregates the result.
1. fork-gate
Always runs. Fails the PR if it was opened from a fork. We never spend CI minutes on (or grant any token to) untrusted code. Maintainers re-land fork PRs from an in-repo branch after review. Produces a real failure (not a neutral "skipped") so branch protection can require it.
2. lint-tests
Needs fork-gate. Validates the codebase that produces skills: ruff format --check, ruff check, pytest -q. If the generator/validator itself is broken, validating the skill output is meaningless, so this runs first.
3. skills
Needs lint-tests. Runs the validator against only the skills the PR touched, diffed against the base branch: uv run python scripts/validate_skill.py --base "origin/${{ github.base_ref }}". This is what enforces the SKILL.md spec.
4. results required
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 jobs can be added or renamed in the workflow file without touching branch protection. skipped counts as a pass on purpose, so future if:-filtered jobs don't false-fail the gate.
Distribution: how a merged change becomes installable
There is no publish step. Once a PR merges to main, the new content is the released content. Consumers install it (or pick up the update) with:
npx skills add bcgov/agent-skills # all public skills
npx skills add bcgov/agent-skills --skill X # just one
The skills CLI reads the repo directly (no registry, no token), walks every SKILL.md, and copies the containing folder into the right place for the chosen agent. See Consume a Skill for the full flow, including pinning to a specific commit.
main state of the repo is the release; nothing in the repo carries a version number, and there is no "publish failed because the version wasn't bumped" failure mode.
Dependabot auto-merge
Dependabot opens grouped upgrade PRs daily with Conventional Commit prefixes (deps(actions) for GitHub Actions, deps(uv) for Python). Cadence and ecosystems are configured in .github/dependabot.yml. When the PR's author is dependabot[bot], an in-repo workflow approves it and turns on auto-merge, so it squash-merges itself the moment the same results check goes green. If the upgrade breaks something, results stays red and the PR sits there until a human looks at it.
The auto-merge workflow lives alongside the rest of CI under .github/workflows/. The merge gate it has to clear is exactly the one humans clear: there is no special path around results, and the workflow itself only acts on PRs Dependabot opened.
Branch protection: main ruleset 17296594
The rules that keep main safe:
| Control | Setting |
|---|---|
| Pull request required | Yes (no approval count enforced; reviewers weigh in by convention) |
| Required status check | results, strict (branch must be up to date with main) |
| Signed commits | Required |
| Code scanning | CodeQL via GitHub's default code-scanning setup (fails on high or above). Configured in Settings → Code security; no codeql.yml in .github/workflows/. |
Force-push to main | Blocked |
Delete main | Blocked |
| Bypass actors | None |
Because the only required check is results, the PR workflow can add, rename, or restructure jobs over time without anyone having to update the protection ruleset.
Action pinning policy
The policy is below; what's actually pinned right now lives in the workflows under .github/workflows/.
First-party (actions/*)
Pinned to a major-version tag. GitHub guarantees those tags forward-only, so a major-tag pin is safe to read. Dependabot still bumps majors.
uses: actions/checkout@v<major>
uses: actions/setup-node@v<major>
Third-party
Pinned to an immutable commit SHA, with the human-readable version in a trailing comment. Defends against tag-hijack / supply-chain attacks. Dependabot bumps these too.
uses: <owner>/<action>@<40-char-sha> # v<version>
Permissions model
- PR workflow: top-level
permissions: contents: read. Every job is read-only; nothing here needs to write back to the repo. - Dependabot auto-merge:
contents: write+pull-requests: write. Required to approve and auto-merge, but the merge itself still has to clear theresultsgate.
There is no publish workflow and no packages: write token anywhere in this repo — distribution happens client-side via npx skills add reading the repo directly.
References in the repo
Everything described above lives in two directories. Trust the directories themselves rather than the file names inside them, so this page doesn't go stale every time a file is added, renamed, or split:
.github/workflows/: PR validation, Dependabot auto-merge, docs deploy.scripts/: the Python validator and its tests.