diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml index a9a3d6d..8909796 100644 --- a/.github/workflows/quality-checks.yml +++ b/.github/workflows/quality-checks.yml @@ -90,7 +90,7 @@ jobs: - name: Validate baseline schema run: | ERRORS=0 - for section in repo_settings security branch_protection labels required_files; do + for section in repo_settings security branch_protection rulesets labels required_files; do if ! jq -e ".$section" config/baseline.json > /dev/null 2>&1; then echo "ERROR: Missing section '$section' in baseline.json" ERRORS=$((ERRORS + 1)) diff --git a/CLAUDE.md b/CLAUDE.md index 6573ac7..81e04fc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -100,10 +100,11 @@ The sync script (`scripts/sync-repo-settings.sh`) enforces: alerts 3. **Branch protection**: reviews, CODEOWNERS, linear history, conversation resolution -4. **Labels**: standard issue labels across all repos -5. **Default branch**: ensures all repos use `main` -6. **Metadata**: flags missing descriptions and topics (advisory) -7. **Required files**: LICENSE, README, CODEOWNERS, etc. +4. **Rulesets**: Copilot code review ruleset on default branch +5. **Labels**: standard issue labels across all repos +6. **Default branch**: ensures all repos use `main` +7. **Metadata**: flags missing descriptions and topics (advisory) +8. **Required files**: LICENSE, README, CODEOWNERS, etc. Configuration lives in `config/baseline.json` with per-repo overrides in `config/overrides.json`. diff --git a/README.md b/README.md index ab6a05e..3e4a326 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,18 @@ ensures clean `git log` and bisectability. | Required conversation resolution | `true` | All comments must be resolved | | Enforce admins | `false` | Admins can bypass when needed | +### Rulesets + +The Copilot code review ruleset is enforced on every repository's +default branch. The sync script creates it if missing and verifies +enforcement is active. + +| Rule | Purpose | +| --- | --- | +| `deletion` | Prevent branch deletion | +| `non_fast_forward` | Prevent force pushes | +| `copilot_code_review` | Require Copilot review on PRs | + ### Labels Standard labels are created on every repo for consistent issue @@ -272,8 +284,15 @@ Add repo names to the `excluded` array in `config/overrides.json`: ## Code Review +- **GitHub Copilot**: auto-review via ruleset (enforced automatically + by the sync script) - **CodeRabbit**: auto-review on PRs via `.coderabbit.yaml` -- **GitHub Copilot**: auto-review via ruleset with custom instructions + +> **Note:** CodeRabbit must be enabled manually per repository through +> the [CodeRabbit dashboard](https://app.coderabbit.ai). There is no +> API to automate this. After installing the GitHub App, select "All +> repositories" to cover new repos automatically, or add repos +> individually through the dashboard. ## CI/CD Pipelines diff --git a/config/baseline.json b/config/baseline.json index de3a9f2..43304bb 100644 --- a/config/baseline.json +++ b/config/baseline.json @@ -66,6 +66,30 @@ "description": "New repository discovered" } ], + "rulesets": { + "copilot_code_review": { + "name": "Copilot review for default branch", + "enforcement": "active", + "target": "branch", + "conditions": { + "ref_name": { + "include": ["~DEFAULT_BRANCH"], + "exclude": [] + } + }, + "rules": [ + {"type": "deletion"}, + {"type": "non_fast_forward"}, + { + "type": "copilot_code_review", + "parameters": { + "review_on_push": false, + "review_draft_pull_requests": false + } + } + ] + } + }, "required_files": [ "LICENSE", "README.md", diff --git a/scripts/sync-repo-settings.sh b/scripts/sync-repo-settings.sh index 8ff73f1..8b3375a 100755 --- a/scripts/sync-repo-settings.sh +++ b/scripts/sync-repo-settings.sh @@ -373,6 +373,71 @@ sync_labels() { echo -e "$changes" } +# Ensure Copilot code review ruleset exists +sync_rulesets() { + local repo="$1" + local effective + effective=$(get_effective_settings "$repo") + local changes="" + + local ruleset_name + ruleset_name=$(echo "$effective" | jq -r '.rulesets.copilot_code_review.name // empty') + if [ -z "$ruleset_name" ]; then + echo "" + return + fi + + # List rulesets — bail if the API call itself fails + local rulesets_json existing + if ! rulesets_json=$(gh api "repos/$OWNER/$repo/rulesets" 2>/dev/null); then + log "WARN: Could not list rulesets for $repo" + echo "" + return + fi + existing=$(echo "$rulesets_json" | jq -r --arg name "$ruleset_name" '.[] | select(.name == $name) | .id' | head -n1) + + local desired_ruleset + desired_ruleset=$(echo "$effective" | jq '.rulesets.copilot_code_review') + + if [ -n "$existing" ]; then + # Compare full ruleset config, not just enforcement + local current_ruleset desired_normalized current_normalized + current_ruleset=$(gh api "repos/$OWNER/$repo/rulesets/$existing" 2>/dev/null || echo "") + desired_normalized=$(echo "$desired_ruleset" | jq -cS '{name, enforcement, target, conditions, rules}') + current_normalized=$(echo "$current_ruleset" | jq -cS '{name, enforcement, target, conditions, rules}') + if [ "$current_normalized" != "$desired_normalized" ]; then + changes="- Copilot review ruleset: configuration drift detected\n" + if [ "$MODE" = "--apply" ]; then + if gh api -X PUT "repos/$OWNER/$repo/rulesets/$existing" \ + --input <(echo "$desired_ruleset") \ + > /dev/null 2>&1; then + log "APPLIED Copilot review ruleset for $repo" + else + log "WARN: Could not update ruleset for $repo" + fi + else + log "DRIFT detected in Copilot review ruleset for $repo" + fi + else + log "OK: Copilot review ruleset for $repo" + fi + else + changes="- Copilot review ruleset: **missing** -> will be created\n" + if [ "$MODE" = "--apply" ]; then + if gh api -X POST "repos/$OWNER/$repo/rulesets" \ + --input <(echo "$desired_ruleset") \ + > /dev/null 2>&1; then + log "APPLIED Copilot review ruleset for $repo" + else + log "WARN: Could not create ruleset for $repo" + fi + else + log "DRIFT detected: missing Copilot review ruleset for $repo" + fi + fi + echo -e "$changes" +} + # Check default branch matches config check_default_branch() { local repo="$1" @@ -513,6 +578,13 @@ REPORT_HEADER repo_drift="${repo_drift}### Branch Protection\n\n${protection_changes}\n" fi + # Rulesets (Copilot code review) + local ruleset_changes + ruleset_changes=$(sync_rulesets "$repo") + if [ -n "$ruleset_changes" ]; then + repo_drift="${repo_drift}### Rulesets\n\n${ruleset_changes}\n" + fi + # Labels local label_changes label_changes=$(sync_labels "$repo")