Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/quality-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
9 changes: 5 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
24 changes: 24 additions & 0 deletions config/baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
72 changes: 72 additions & 0 deletions scripts/sync-repo-settings.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +402 to +423
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sync_rulesets only checks whether enforcement is active, but it doesn’t validate the rest of the ruleset (rules/conditions/target). That means a repo can be reported as OK even if protections like deletion / non_fast_forward / copilot_code_review were changed or removed. Consider diffing the current ruleset JSON against the desired config (after normalizing away server-managed fields like id, timestamps, etc.) and treating any mismatch as drift (and applying an update in --apply mode).

Copilot uses AI. Check for mistakes.
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"
Expand Down Expand Up @@ -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")
Expand Down
Loading