Skip to content
Merged
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ description: "GitHub repository setup and platform-specific features. This skill

GitHub platform configuration and repository management patterns. This skill focuses exclusively on **GitHub-specific features**.

> **New repo? Run this BEFORE the first PR:**
> `bash skills/github-project/scripts/init-branch-protection.sh OWNER/REPO`
> Applies `required_conversation_resolution: true` + 1-approver baseline so the
> "abort merge if unresolved threads" rule is structurally enforced, not just
> documented. See `skills/github-project/SKILL.md` → *Required First Step
> After `gh repo create`* for the two-step flow.

## 🔌 Compatibility

This is an **Agent Skill** following the [open standard](https://agentskills.io) originally developed by Anthropic and released for cross-platform use.
Expand Down
76 changes: 36 additions & 40 deletions skills/github-project/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: github-project
description: "Use when PRs won't merge or show BLOCKED (Copilot-review race), AI reviewer pushback, auto-approve/auto-merge fails for Dependabot/Renovate, branch protection/rulesets need configuring, CI fails, authoring reusable workflows or composite actions, harden-runner setup, or CODEOWNERS / PR templates."
description: "Use when bootstrapping a repo (apply branch protection before first PR), PRs won't merge or BLOCKED, AI reviewer pushback, auto-merge fails for Dependabot/Renovate, branch protection or rulesets, CI fails, authoring reusable workflows, harden-runner, or CODEOWNERS/PR templates."
license: "(MIT AND CC-BY-SA-4.0). See LICENSE-MIT and LICENSE-CC-BY-SA-4.0"
compatibility: "Requires gh CLI, git."
metadata:
Expand All @@ -16,28 +16,27 @@ GitHub repository configuration, troubleshooting, and collaboration workflow bes

## When to Use

- **Post `gh repo create` + initial push, before first PR** — apply branch protection (REQUIRED, see below)
- PR won't merge, BLOCKED, or unresolved threads
- Auto-merge fails for Dependabot/Renovate
- Solo maintainer needs auto-approve
- Solo maintainer auto-approve
- Branch protection, rulesets, `enforce_admins`
- GHA failures or permission issues
- Signed commit merge (rebase can't auto-sign)
- CodeQL default vs custom workflows
- OpenSSF Scorecard (token perms, pinned deps)
- CODEOWNERS, issue/PR templates, release labels
- Fork PR merge base (too many commits)
- Scorecard (token perms, pinned deps)
- CODEOWNERS, templates, release labels
- Fork PR merge base

> **REQUIRED post `gh repo create`:** `scripts/init-branch-protection.sh OWNER/REPO` — see `references/repo-bootstrap.md` (closes [snipe-it#17](https://github.com/netresearch/snipe-it-docker-compose-stack/pull/17) class).

## Quick Diagnostics

### PR Won't Merge

```bash
gh api graphql -f query='query($owner:String!,$repo:String!,$pr:Int!){
repository(owner:$owner,name:$repo){pullRequest(number:$pr){
mergeStateStatus reviewDecision mergeable
reviewThreads(first:100){nodes{isResolved comments(first:1){nodes{body}}}}
}}
}' -f owner=OWNER -f repo=REPO -F pr=NUMBER --jq '.data.repository.pullRequest'
gh pr view PR --repo OWNER/REPO \
--json mergeStateStatus,reviewDecision,mergeable,reviewThreads
```

### Solo Maintainer: PRs Stuck on REVIEW_REQUIRED
Expand All @@ -46,15 +45,12 @@ Use `assets/pr-quality.yml.template` for auto-approve with `required_approving_r

### Auto-merge Setup

Requirements: `allow_auto_merge` on repo, `pull_request_target` trigger (not `pull_request`), check `user.login` (not `github.actor`), `gh pr merge --auto` with dynamic strategy.
Requires `allow_auto_merge`, `pull_request_target` trigger, `user.login` bot detection, `gh pr merge --auto` with dynamic strategy. See `references/auto-merge-guide.md`.

### Auto-merge Not Working

```bash
gh api graphql -f query='query{repository(owner:"OWNER",name:"REPO"){
pullRequest(number:PR){autoMergeRequest{enabledBy{login}}}
}}' --jq '.data.repository.pullRequest.autoMergeRequest'

gh pr view PR --repo OWNER/REPO --json autoMergeRequest --jq .autoMergeRequest
gh api repos/OWNER/REPO/branches/main/protection/required_pull_request_reviews \
--jq '.bypass_pull_request_allowances.apps[].slug'
```
Expand All @@ -70,50 +66,50 @@ gh run rerun RUN_ID --repo OWNER/REPO
### Security & Compliance Quick Checks

```bash
gh api repos/OWNER/REPO/branches/main/protection --jq '.enforce_admins.enabled'
gh api repos/OWNER/REPO/branches/main/protection \
--jq '{rcr: .required_conversation_resolution.enabled, admins: .enforce_admins.enabled}'
gh api repos/OWNER/REPO/code-scanning/default-setup --jq '.state'
gh api graphql -f query='query($owner:String!,$repo:String!,$pr:Int!){
repository(owner:$owner,name:$repo){pullRequest(number:$pr){
reviewThreads(first:100){nodes{id isResolved}}
}}
}' -f owner=OWNER -f repo=REPO -F pr=NUMBER
gh pr view PR --repo OWNER/REPO --json reviewThreads --jq '.reviewThreads'
```

### Merge Strategy Issues

See `references/auto-merge-guide.md` for: rebase-merge-with-signed-commits fixes, workflow-file PR manual merges, and the Copilot-review auto-approve race.
See `references/auto-merge-guide.md` (signed-commit rebase fixes, workflow-file PRs, Copilot auto-approve race).

## Running Scripts

```bash
scripts/verify-github-project.sh /path/to/repository
scripts/init-branch-protection.sh OWNER/REPO # baseline (post gh repo create)
scripts/init-branch-protection.sh OWNER/REPO --from-current-checks # after first CI
scripts/verify-github-project.sh /path/to/repository # local-checkout audit
```

## References

| Topic | Reference |
|-------|-----------|
| Repo bootstrap (post `gh repo create`) | `references/repo-bootstrap.md` |
| Repository file layout | `references/repository-structure.md` |
| Branch migration (master to main) | `references/branch-migration.md` |
| Dependabot/Renovate configuration | `references/dependency-management.md` |
| Branch migration | `references/branch-migration.md` |
| Dependabot/Renovate | `references/dependency-management.md` |
| Auto-approve + auto-merge | `references/auto-merge-guide.md` |
| Merge strategy for signed commits | `references/merge-strategy.md` |
| Sub-issues and issue hierarchy | `references/sub-issues.md` |
| Release labeling automation | `references/release-labeling.md` |
| Merge strategy (signed commits) | `references/merge-strategy.md` |
| Sub-issues | `references/sub-issues.md` |
| Release labeling | `references/release-labeling.md` |
| gh CLI commands | `references/gh-cli-reference.md` |
| Go, TYPO3, polyglot CI checklists | `references/repo-setup-guide.md` |
| OpenSSF Scorecard, CodeQL, security | `references/security-config.md` |
| Workflow linting (actionlint) | `references/actionlint-guide.md` |
| Bash pitfalls in workflow `run:` steps | `references/workflow-bash-patterns.md` |
| PR shows too many commits (fork merge base) | `references/pr-commit-cleanup.md` |
| Polyglot CI checklists | `references/repo-setup-guide.md` |
| Scorecard, CodeQL, security | `references/security-config.md` |
| actionlint | `references/actionlint-guide.md` |
| Workflow bash pitfalls | `references/workflow-bash-patterns.md` |
| Fork merge base | `references/pr-commit-cleanup.md` |
| Multi-repo batch ops | `references/multi-repo-operations.md` |
| Reusable workflow supply-chain trust + SHA pinning | `references/reusable-workflow-security.md` |
| Reusable workflow pitfalls (composite actions, ref caching, permissions) | `references/reusable-workflow-pitfalls.md` |
| Org-level security settings (SHA pinning) | `references/org-security-settings.md` |
| Tag validation (defense-in-depth) | `references/tag-validation.md` |
| AI reviewer pushback patterns | `references/ai-reviewer-pushback.md` |
| Reusable workflow security | `references/reusable-workflow-security.md` |
| Reusable workflow pitfalls | `references/reusable-workflow-pitfalls.md` |
| Org security settings | `references/org-security-settings.md` |
| Tag validation | `references/tag-validation.md` |
| AI reviewer pushback | `references/ai-reviewer-pushback.md` |
| Agentic workflows | `references/agentic-workflows.md` |

---

> **Contributing:** https://github.com/netresearch/github-project-skill
> Contributing: https://github.com/netresearch/github-project-skill
17 changes: 17 additions & 0 deletions skills/github-project/assets/branch-protection.json.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"required_status_checks": null,
"enforce_admins": false,
"required_pull_request_reviews": {
"required_approving_review_count": 1,
"dismiss_stale_reviews": false,
"require_code_owner_reviews": false,
"require_last_push_approval": false
},
"restrictions": null,
"required_linear_history": false,
"allow_force_pushes": false,
"allow_deletions": false,
"required_conversation_resolution": true,
"lock_branch": false,
"allow_fork_syncing": false
}
16 changes: 12 additions & 4 deletions skills/github-project/checkpoints.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -344,10 +344,18 @@ mechanical:
json_path: ".required_conversation_resolution.enabled"
severity: error
desc: >-
required_conversation_resolution must be enabled — combined with
enforce_admins, ensures unresolved review threads block ALL merges
including admins (audited by GH-32 llm_review; runner skips because
gh API access requires auth).
required_conversation_resolution must be enabled. Without it, PR
merges may silently include unresolved bot-reviewer feedback —
including security issues like token leakage in build logs (see
netresearch/snipe-it-docker-compose-stack#17 where this gap let a
HIGH-severity Copilot/gemini-code-assist finding ship to main).
Apply via skills/github-project/scripts/init-branch-protection.sh
OWNER/REPO right after `gh repo create` + initial push, before
opening the first PR (script requires the default branch ref to
exist; exits 4 on empty repos). Combined with enforce_admins,
also ensures unresolved threads block ALL merges including admins
(audited by GH-32 llm_review; runner skips because gh API access
requires auth).

llm_reviews:
# === BRANCH PROTECTION + MERGE QUEUE COMPATIBILITY ===
Expand Down
67 changes: 67 additions & 0 deletions skills/github-project/references/repo-bootstrap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Repository Bootstrap — Required First Step After `gh repo create`

After creating any new Netresearch repository — `gh repo create`, push your initial commit, **then before opening the first PR** — you MUST apply branch protection. (The default branch ref must exist; the script exits 4 on empty repos.) Without this, the unresolved-threads workflow rule is unenforceable — operator discipline alone has demonstrably failed.

**Concrete incident:** [netresearch/snipe-it-docker-compose-stack#17](https://github.com/netresearch/snipe-it-docker-compose-stack/pull/17). The repo was created mid-session, branch protection was never applied, and three of the next eight merged PRs shipped with unresolved bot-reviewer threads — including a HIGH-severity token leak that both Copilot and gemini-code-assist had flagged. The structural enforcement (`required_conversation_resolution: true`) would have blocked those merges. The skill had the docs; nothing prompted the apply.

## Two-step flow

```bash
# 1. Immediately after `gh repo create` and the first push:
bash <skill-root>/skills/github-project/scripts/init-branch-protection.sh OWNER/REPO
# Applies the baseline:
# required_conversation_resolution: true (load-bearing)
# required_approving_review_count: 1
# allow_force_pushes: false
# allow_deletions: false
# required_linear_history: false (needed for merge-commit
# signing strategy)
# Required status checks are intentionally NOT set yet — a brand-new repo
# has no CI history to discover context names from.

# 2. After the first CI run completes on the default branch:
bash <skill-root>/skills/github-project/scripts/init-branch-protection.sh OWNER/REPO --from-current-checks
# Discovers successful check-run names from /commits/{default}/check-runs
# and PATCHes them in as required contexts with strict=true.
```

The script is idempotent: re-running on an already-compliant repo reports `already compliant` and exits 0. Drift on opinionated fields exits 1 with a per-field diff (no silent clobber of admin choices).

## Deliberately permissive knobs

- **`enforce_admins`** — explicitly `false` in the template. Solo-maintainer Netresearch repos (snipe-it-docker-compose-stack, ldap-selfservice-…, usercentrics-widgets, etc.) need admin bypass for emergency response. Tighten per-repo once the team validates the workflow:
```bash
gh api repos/OWNER/REPO/branches/DEFAULT/protection/enforce_admins -X POST
```
- **`required_signatures`** — *omitted entirely* from the template (not set to `false`). PUTting the template would otherwise reset repos that have already opted into signing. The script never touches this field. Tighten per-repo:
```bash
gh api repos/OWNER/REPO/branches/DEFAULT/protection/required_signatures -X POST
```

Both knobs flip to `true` only after the team has signing infrastructure for bot accounts (Dependabot, Renovate) so those PRs don't immediately get blocked.

## Verification

Read-only audit of an existing repo:

```bash
gh api repos/OWNER/REPO/branches/$(gh api repos/OWNER/REPO --jq .default_branch)/protection \
--jq '.required_conversation_resolution.enabled // false'
```

Or invoke `/assess github-project` — checkpoint `GH-31` fails with `severity: error` if `required_conversation_resolution` is not enabled, with a `desc:` that names this exact failure mode.

## Gap NOT closed by the baseline

GitHub branch protection cannot block on *requested-but-pending* reviews (Copilot is mid-review, you merge anyway). The baseline closes the **unresolved-threads** class (which is what snipe-it#17 slipped through), not the **pending-reviewer** class. The pending-reviewer gap is a workflow-discipline rule audited via the GraphQL `reviewRequests` check before any merge — see `references/security-config.md` § "Required Reviews from All Requested Reviewers".

## Script exit codes

| Code | Meaning |
|------|---------|
| 0 | Applied, or already compliant |
| 1 | Drift detected on opinionated fields (per-field diff printed); script refuses to clobber |
| 2 | Invalid arguments / template missing |
| 3 | Repo not found or no API access |
| 4 | Default branch ref does not yet exist (empty repo — push first) |
| 5 | `--from-current-checks`: no completed CI run on default branch |
Loading
Loading