Files
knowledge-work-plugins/.github/workflows/external-pr-scope-guard.yml
Bryan ThompsonandClaude Opus 4.8 f540d5605f chore(ci): add external-PR curation workflows (port from claude-plugins-official) (#428)
knowledge-work-plugins is a curated surface, but unlike claude-plugins-official
it had no gate on external pull requests — so the open-PR queue had accumulated
~87 external contributions (README edits, "fixes" to first-party plugins,
unsolicited new community plugins) that we do not accept here.

Ports the established -official mechanism verbatim:

- close-external-prs.yml — on PR opened, if the author is not an org member
  (write/admin) and the PR is not an in-scope contribution, posts the standard
  redirect comment (submit via clau.de/plugin-directory-submission) and closes it.
- external-pr-scope-guard.yml — advisory (NOT required) check surfacing the
  in-scope carve-out for reviewers.
- .github/scripts/external-pr-scope.js — shared trust-the-source-repo logic
  (MARKETPLACE = .claude-plugin/marketplace.json, matches this repo).

Carve-out (verbatim from -official): a non-member PR may stay open only if it
ADDS marketplace.json entries whose source repo already backs a live plugin
here, and changes nothing else. Members + the bump bot are exempt. Kill switch:
repo var DISABLE_EXTERNAL_PR_CHECK=true.

Prospective only (fires on `opened`) — does not touch the existing open queue.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 19:58:31 -05:00

55 lines
2.4 KiB
YAML

name: External PR Scope Guard
# Advisory check that surfaces what a NON-MEMBER pull request may change.
# Members (write/admin) and the repo's own automation bot (bump SHA PRs) are unrestricted and
# skip this check. For a non-member PR this fails unless the PR is an in-scope external
# contribution per .github/scripts/external-pr-scope.js: it changes ONLY
# .claude-plugin/marketplace.json, the delta is additions-only (no existing entry modified or
# removed), and every ADDED entry's source.url is a repo that ALREADY backs a live plugin in
# this marketplace (the allowed set is derived from the live marketplace — there is no
# maintained allowlist).
#
# Do NOT add this job to branch protection as a required status check. The merge gate is the
# `validate` + `scan` checks plus a maintainer approval; this guard is advisory signal for the
# reviewer, not a hard gate. (Making it required would block the no-approval bump-merge path.)
#
# Security: runs on pull_request_target but checks out only the BASE repo (trusted) for the
# shared script; the head marketplace.json is fetched as DATA via the API and parsed, never executed.
on:
pull_request_target:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: read
jobs:
scope-guard:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # base repo (trusted)
- uses: actions/github-script@v7
with:
script: |
const { evaluate, isExemptAuthor } = require(`${process.env.GITHUB_WORKSPACE}/.github/scripts/external-pr-scope.js`);
// Members (write/admin) and the repo's own automation bot (bump SHA PRs) are
// unrestricted; only genuinely external contributions are scope-checked.
const ex = await isExemptAuthor({ github, context });
if (ex.exempt) {
console.log(`${ex.reason} — scope guard not applicable.`);
return;
}
const result = await evaluate({ github, context });
if (!result.ok) {
core.setFailed(
`Scope guard: a non-member PR may only ADD marketplace.json entries whose source repo already backs a live plugin here.\n - ` +
result.problems.join('\n - ')
);
return;
}
console.log(`Scope guard passed: adds ${result.added.join(', ') || 'none'}, all from repos already live here.`);