Branch ruleset setup
Skill emaarco/hogwarts/plugins/felix-felicis/skills/branch-ruleset-setup
A magical place where my skills, rules, and plugins for AI agents are defined, which magically boost productivity π°πͺ
npx -y skills add emaarco/hogwarts --skill branch-ruleset-setupAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Sets up a GitHub branch ruleset protecting the default branch via the gh CLI: no deletion, no force-push, linear history, signed commits, PR-only changes, and a required CI status check with a dynamically resolved integration_id. Idempotent β creates the ruleset or updates an existing one. Use when asked to protect the default branch, set up branch protection or rulesets, or require status checks before merge.
SKILL.md
6.6 KB, as published. Nobody here has run it
Skill: branch-ruleset-setup
Configures a repository ruleset (not classic branch protection) named main targeting the default branch (~DEFAULT_BRANCH). Everything runs through the gh CLI with the ruleset passed as inline JSON via heredoc β never create a ruleset JSON file in the repo.
Run this when asked to "protect main", set up branch protection / a branch ruleset, or as the branch-protection slice of a release/supply-chain audit (see the sibling skill release-audit).
Precondition: gh auth status must be authenticated with admin rights on the target repo.
Phase 0 β Ensure a referenceable CI check exists
A required status check only makes sense if the CI job actually exists. Never hardcode the check name β detect it:
ls .github/workflows/ 2>/dev/null
grep -rl "pull_request" .github/workflows/ 2>/dev/null
-
A
pull_request-triggered workflow exists β note its job name (= the status-checkcontext). If the job is calledciortestinstead ofbuild, use that name below. -
No workflow exists β create a minimal one first, commit it to the default branch, and let it run at least once so the check context exists. Example (
.github/workflows/build.ymlβ adapt steps to the repo's actual stack):name: Build on: pull_request: branches: [main] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: '24' - run: npm ci - run: npm test - run: npm run build
Phase 1 β Resolve the integration_id dynamically
The check is reported by GitHub Actions, whose app ID can vary by context β never hardcode a number like 15368:
ACTIONS_APP_ID=$(gh api /apps/github-actions --jq .id)
echo "GitHub Actions integration_id = ${ACTIONS_APP_ID}"
If the check is reported by a different CI provider, resolve its app ID from a commit where the check already ran:
gh api repos/{owner}/{repo}/commits/<sha>/check-runs --jq '.check_runs[] | {name, app: .app.id}'
Phase 2 β Check for an existing ruleset (idempotency)
RULESET_ID=$(gh api repos/{owner}/{repo}/rulesets --jq '.[] | select(.name=="main") | .id')
- Empty β create with
POST(Phase 3). - Present β update with
PUTon.../rulesets/${RULESET_ID}β same body, different method/URL β so no duplicate is created.
Phase 3 β Create or update the ruleset
The integration_id is injected via an unquoted heredoc (<<JSON without quotes is intentional β only then does ${ACTIONS_APP_ID} expand; the body contains no other $):
gh api --method POST repos/{owner}/{repo}/rulesets \
-H "Accept: application/vnd.github+json" --input - <<JSON
{
"name": "main",
"target": "branch",
"enforcement": "active",
"conditions": {
"ref_name": { "include": ["~DEFAULT_BRANCH"], "exclude": [] }
},
"rules": [
{ "type": "deletion" },
{ "type": "non_fast_forward" },
{ "type": "required_linear_history" },
{ "type": "required_signatures" },
{
"type": "pull_request",
"parameters": {
"required_approving_review_count": 0,
"dismiss_stale_reviews_on_push": false,
"require_code_owner_review": false,
"require_last_push_approval": false,
"required_review_thread_resolution": false,
"allowed_merge_methods": ["merge", "squash", "rebase"]
}
},
{
"type": "required_status_checks",
"parameters": {
"strict_required_status_checks_policy": false,
"do_not_enforce_on_create": false,
"required_status_checks": [
{ "context": "build", "integration_id": ${ACTIONS_APP_ID} }
]
}
}
],
"bypass_actors": []
}
JSON
Replace "build" with the actual job name from Phase 0.
Phase 4 β Verify
RULESET_ID=$(gh api repos/{owner}/{repo}/rulesets --jq '.[] | select(.name=="main") | .id')
gh api repos/{owner}/{repo}/rulesets/${RULESET_ID} --jq '{enforcement, target}'
gh api repos/{owner}/{repo}/rulesets/${RULESET_ID} --jq '.rules[].type'
gh api repos/{owner}/{repo}/rulesets/${RULESET_ID} \
--jq '.rules[] | select(.type=="required_status_checks") | .parameters.required_status_checks'
Success criteria:
- Exactly one ruleset
main,enforcement: active,target: branch, targeting~DEFAULT_BRANCH. .rules[].typecontains:deletion,non_fast_forward,required_linear_history,required_signatures,pull_request,required_status_checks.- The required status check has the correct context (job name from Phase 0) and a resolved
integration_id(not empty, not hardcoded).
What the rules do & how to adapt them
| Rule | Effect | Adapt when |
|---|---|---|
deletion | Default branch cannot be deleted | β |
non_fast_forward | No force-pushes | β |
required_linear_history | Linear history enforced | Drop if the team uses merge commits from long-lived branches |
required_signatures | Commits must be signed (GPG/SSH/S-MIME) | Requires signing to be set up for all committers β otherwise pushes/merges are rejected; drop if not. Note: commits made via the API by GitHub Apps (e.g. release-please with an app token β see release-please-setup) are signed by GitHub automatically |
pull_request | Changes only via PR; 0 approvals by default | Raise required_approving_review_count to 1+ for teams |
required_status_checks | CI check must be green before merge | Omit integration_id to match any provider reporting that context (less strict); remove the whole block if there is no CI |
bypass_actors: [] | Nobody can bypass, not even admins | Add e.g. { "actor_id": <id>, "actor_type": "Team", "bypass_mode": "always" } for a deliberate bypass |
Sources
- Repository rulesets REST API: https://docs.github.com/en/rest/repos/rules
- About rulesets: https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets
- Reference implementation: the active ruleset on https://github.com/emaarco/slidev-addon-bpmn