Skip to content
Draft
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
47 changes: 47 additions & 0 deletions .github/workflows/repo-policy-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
name: Repository policy sync
on:
workflow_dispatch:
permissions:
contents: read
pull-requests: read
jobs:
policy-sync:
runs-on: ubuntu-24.04
env:
GH_TOKEN: ${{ github.token }}
steps:
- name: Checkout repository
uses: actions/checkout@v7.0.0
- name: Setup uv
uses: astral-sh/setup-uv@v7
- name: Generate policy reports
id: sync
continue-on-error: true
run: >-
uv run score-repo-policy-sync --config repo_policy_sync/eclipse-score.toml --json-output repo-policy-sync-report.json --markdown-output "$GITHUB_STEP_SUMMARY"
- name: Upload JSON report
if: always()
uses: actions/upload-artifact@v7
with:
name: repo-policy-sync-report
path: repo-policy-sync-report.json
if-no-files-found: error
- name: Report policy sync status
if: always()
run: |
if [[ "${{ steps.sync.outcome }}" != "success" ]]; then
echo "Repository policy sync detected drift or failed to complete." >&2
exit 1
fi
14 changes: 12 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ jobs:
# run: |
# cd python_basics/integration_tests
# bazel test //...
- name: Run cr_checker unit tests
- name: Run Python tests
run: uv run --locked pytest --ignore-glob='bazel-*'
- name: Run Ruff quality checks
run: |
uv run pytest cr_checker/tests/
uv run --locked ruff check .
uv run --locked ruff format --check .
- name: Build the Python wheel
run: uv build --wheel
- name: Smoke-test the installed Repository Policy Sync CLI
run: |
wheel=$(find dist -maxdepth 1 -name '*.whl' -print -quit)
uv run --no-project --with "$wheel" score-copyright --help
uv run --no-project --with "$wheel" score-repo-policy-sync --help
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ repos:
hooks:
- id: copyright
name: Check and fix copyright headers with cr_checker
entry: cr_checker/tool/cr_checker.py --exclusion copyright_exclusions.txt --fix
entry: cr_checker/tool/cr_checker.py --exclusion-file copyright_exclusions.txt --fix
language: script
minimum_pre_commit_version: 3.2.0
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,10 @@ Before adding a new utility here, check:
consumers better than adding it here?

If any of these gives you pause, raise it for discussion before merging.

## Repository Policy Sync

The [`repo_policy_sync`](repo_policy_sync/README.md) component evaluates and
optionally remediates repository policies across a GitHub organization. Its
supported entry point is `score-repo-policy-sync`; start with plan mode and
use apply mode only after reviewing the generated changes.
5 changes: 2 additions & 3 deletions cr_checker/tests/test_cr_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def test_detect_shebang_offset_counts_trailing_newlines(tmp_path):
"rst",
]
)
def prepare_test_with_header(request: SubRequest, tmp_path: PosixPath) -> tuple:
def prepare_test_with_header(request, tmp_path: Path) -> tuple:
extension = request.param
test_file = tmp_path / ("file." + extension)
header_template = load_template(extension)
Expand Down Expand Up @@ -103,11 +103,10 @@ def prepare_test_with_header(request: SubRequest, tmp_path: PosixPath) -> tuple:
"rst",
]
)
def prepare_test_no_header(request: SubRequest, tmp_path: PosixPath) -> tuple:
def prepare_test_no_header(request, tmp_path: Path) -> tuple:
extension = request.param
test_file = tmp_path / ("file." + extension)
header_template = load_template(extension)
current_year = datetime.now().year
test_file.write_text(
"some content\n",
encoding="utf-8",
Expand Down
22 changes: 22 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,27 @@ readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"bazel-runfiles==1.3.0",
"pre-commit>=4.0.0",
"pydantic>=2.0",
"pyyaml>=6.0",
Comment on lines 7 to +11
]

[project.scripts]
score-copyright = "cr_checker.tool.cr_checker:main"
score-repo-policy-sync = "repo_policy_sync.cli:main"

[dependency-groups]
dev = [
"pytest>=9.1.1",
"ruff==0.15.10",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = [
"cr_checker",
"repo_policy_sync",
]
167 changes: 167 additions & 0 deletions repo_policy_sync/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<!-- ----------------------------------------------------------------------------
Copyright (c) 2026 Contributors to the Eclipse Foundation

See the NOTICE file(s) distributed with this work for additional
information regarding copyright ownership.

This program and the accompanying materials are made available under the
terms of the Apache License Version 2.0 which is available at
https://www.apache.org/licenses/LICENSE-2.0

SPDX-License-Identifier: Apache-2.0
----------------------------------------------------------------------------- -->

# SCORE Repository Policy Sync

SCORE Repository Policy Sync continuously evaluates declarative repository
policies across a GitHub organization. It checks each repository's current
default branch and, when requested, opens or updates one reviewable pull
request per policy and repository.

The default mode is safe to use in CI: it only reports drift and makes no
remote changes. Apply mode is deliberately explicit and preserves policy PR
ownership so repeated runs update the same proposal rather than creating
duplicates. When an existing policy PR is already correct but conflicts with
its target branch, apply mode automatically rebuilds it from the current
default branch; body-only changes update the PR text without rebuilding its
branch.

## Quick start

Requirements: Python 3.12+, [uv](https://docs.astral.sh/uv), Git, and an
authenticated [GitHub CLI](https://cli.github.com/).

```bash
uv sync
gh auth login

# Plan with local policies from ./policies, when present, and the bundled SCORE
# policies without changing remote repositories.
uv run score-repo-policy-sync plan --org eclipse-score

# Add another local policy directory when needed.
uv run score-repo-policy-sync plan --org eclipse-score \
--policy-dir shared-policies

# Apply: create or update policy-owned pull requests.
uv run score-repo-policy-sync apply --org eclipse-score

# Collect matching repository files for policy design and fixture review.
uv run score-repo-policy-sync collect-samples \
--org eclipse-score \
--policy score-docs-workflow-alignment \
--output /tmp/score-policy-samples
```

Pre-commit is run again when the first run applies formatting fixes. If the
second run is clean, those fixes are included in the normal pull request. If
pre-commit still fails but the changes should remain reviewable, opt in to a
draft pull request. The failure is added as a PR comment:

```bash
uv run score-repo-policy-sync apply --org eclipse-score --allow-dirty-pr
```

Restrict a run with repeatable `--policy NAME` and `--repo NAME` flags. The
`--policy` option selects local policies; bundled SCORE policies are included
by default and can be removed with `--exclude-bundled-policy NAME`. Policy
names are directory names below the selected policy directories. Use repeated
`--policy-dir PATH` options to combine local policy directories:

```bash
uv run score-repo-policy-sync plan \
--org eclipse-score \
--repo reference_integration \
--policy-dir repo_policy_sync/policies \
--policy minimum-bazel-version

uv run score-repo-policy-sync plan \
--org etas \
--repo reference_integration
```

To exclude a bundled policy for a repository or rollout:

```bash
uv run score-repo-policy-sync plan \
--org etas \
--exclude-bundled-policy minimum-bazel-version
```

Policy options can be kept in the optional `score-repo-policy-sync.toml` file.
Explicit CLI values override the file; see the
[configuration reference](docs/reference/configuration.md).

The CLI always prints a compact table to standard output. Pass
`--json-output PATH` and/or `--markdown-output PATH` to write additional
versioned JSON and Markdown reports during the same policy run. Markdown is
suited for pull requests, issues, and wikis. Its cells use `✅` for compliant,
`❌` for required changes, `N/A` for policies that do not apply, and
`⚠️`/`⏭️` for errors or skipped evaluations. Open, merged, and automatically
closed policy pull requests are shown as linked GitHub-logo badges in the
affected cells; change and error details are kept in a collapsible section.
Plan mode exits `1`
when policy drift is found, `0` when no policy drift is found, and `2` for
input or execution errors. Apply mode exits `0` after successful remediation.

## Operational model

Checkouts are cached under
`$XDG_CACHE_HOME/repo-cache/<owner>/<repository>` or
`~/.cache/repo-cache/<owner>/<repository>`. The generic cache can be shared
with other repository tools. Checkouts are disposable: each run refreshes the
selected repositories to their current default branches before evaluation.
Archived repositories are excluded. Use `--cache-dir PATH` in CI to choose a
workspace-local cache and `--sync-workers N` to control concurrent checkout
synchronization. Policy evaluation and apply work, including policy follow-up
commands such as Bazel, run across independent repositories with
`--policy-workers N`.

To rebuild a policy PR from the current default branch, use the guarded
recreate operation with exactly one repository and policy:

```bash
uv run score-repo-policy-sync apply --org eclipse-score --repo reference_integration \
--policy minimum-bazel-version --recreate
```

## Documentation

The [documentation index](docs/README.md) is organized using the four Diataxis
quadrants:

- **Tutorials:** [create your first policy](docs/tutorials/first-policy.md).
- **How-to guides:** [run a policy](docs/how-to/run-a-policy.md).
- **Reference:** [CLI](docs/reference/cli.md) and
[policy format](docs/reference/policy-format.md) plus the
[bundled policy overview](policies/README.md).
- **Explanation:** [architecture](docs/explanation/architecture.md),
[execution model](docs/explanation/execution-model.md), and
[pull request safety](docs/explanation/pull-request-safety.md).

## First-version interface

The supported executable is `score-repo-policy-sync`. Policy IDs are the
directory names containing each `policy.yml`, and policy-owned branches use
the `repo-policy-sync/<policy-id>` naming scheme. The first version does not
provide command aliases or historical policy-ID compatibility; update callers
to the supported command and current policy IDs before rollout.

## First-version change summary

The first version provides declarative bundled and local policies, fixture-
tested idempotent operations, safe plan mode, explicit apply mode, and
policy-owned pull requests with terminal, JSON, and Markdown reports. It
supports the documented GitHub organization workflow, bounded checkout and
policy concurrency, and recovery from stale or conflicting policy branches.

Compatibility notes:

- Callers must use `score-repo-policy-sync`, `--policy-dir`, the current policy
IDs, and `repo-policy-sync/<policy-id>` branches. Legacy command aliases,
`--policy-directory`, and historical policy IDs are not supported.
- Renaming a policy changes its branch and pull-request identity. Existing
policy branches or pull requests must be handled before adopting the new ID.
- The first version intentionally does not provide dynamic operation plugins,
persistent result storage, generalized retries/rate-limit handling, or
non-GitHub providers.
14 changes: 14 additions & 0 deletions repo_policy_sync/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

"""Organization-wide repository policy synchronization."""
Loading
Loading