- One manifest for many repositories.
ward.tomldescribes the desired state for a whole organization or a set of systems. - Plan before any change.
ward planis read-only.ward applyruns one category at a time, and each category has its own safety boundary. - Verify after apply. Ward reads the state back from GitHub and checks it against the manifest.
- Drift checks for CI.
ward driftexits with0(in sync),1(drift) or2(could not run). - Start from what you have.
ward importturns an existing repository into your baseline manifest. - Measured speed. On 10 repositories,
ward plantook 130.9 s in 0.4.2 and 28.6 s in 0.5.0 (median of 3 runs, 4.6× faster).
# crates.io
cargo install ward-cli
# Homebrew (macOS and Linux)
brew install OriginalMHV/tap/ward-cli
# Shell installer (macOS and Linux)
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/OriginalMHV/Ward/releases/latest/download/ward-cli-installer.sh | sh
# PowerShell (Windows)
powershell -ExecutionPolicy ByPass -c "irm https://github.com/OriginalMHV/Ward/releases/latest/download/ward-cli-installer.ps1 | iex"Building from source needs Rust 1.88 or newer. Ward reads its token from GH_TOKEN, GITHUB_TOKEN or gh auth token. Run ward doctor to check your setup. The GitHub coverage page lists the permissions each category needs.
ward import OWNER/REPO # write ward.toml from an existing repository
ward plan # show what differs from GitHub, change nothing
ward apply --category repository # apply one category, then verify itimportreads the repository through the GitHub API and writes a reviewableward.toml. The source repository is the only target, so the first plan shows zero drift.plancollects the live state, compares it with the manifest and prints the differences.applyshows the plan, asks for confirmation, applies the category and checks the result.
Edit ward.toml, run ward plan again, and repeat. See Getting started for the full walkthrough.
Every category in ward.toml has a policy with a disposition:
| Disposition | Meaning |
|---|---|
managed |
Ward may change GitHub to match the manifest. |
observe |
Ward reports state and coverage. It never writes. |
reference |
Inherited resources, such as organization rulesets. Reported only. |
placeholder |
A value that lives outside the manifest, such as a secret. Reported only. |
ward import starts the repository and files categories as managed. It starts all other categories as observe and sensitive. Change a category to managed when you want Ward to write it. See Configuration.
Pass any of these to --category. Separate several with commas.
| Category | Scope |
|---|---|
repository |
Description, homepage, default branch, merge settings, topics, labels, custom properties, immutable releases |
files |
Configuration files such as .github/**, delivered through a pull request |
security |
Advanced Security, Dependabot, secret scanning, CodeQL default setup, private vulnerability reporting |
rulesets |
Repository rulesets with conditions, rules and bypass actors |
branch-protection |
Detailed protection for the default branch and every protected branch |
actions |
Actions policy, token permissions, retention, variables, secret names, workflow state |
environments |
Environment settings, reviewers, deployment policies, variables, secret names |
access |
Teams, collaborators, invitations, App references |
integrations |
Webhooks, deploy keys, Pages, autolinks |
ward audit supports the security, rulesets, branch-protection and access sections. See GitHub coverage for the full matrix and the public API limits.
| Command | Purpose |
|---|---|
ward import SOURCE |
Write a manifest from an existing repository. |
ward init |
Write a minimal ward.toml scaffold. |
ward doctor |
Check the token, the config, the audit log and API access. |
ward plan |
Show the changes needed to reach the desired state. Read-only. |
ward apply |
Apply the changes per category, then verify. Asks first, or use --yes. |
ward drift |
Compare live state with ward.toml. Exit code 0, 1 or 2. |
ward audit |
Report security, rulesets, branch protection and access across repositories. |
ward repos list |
List repositories with metadata. |
ward config show |
Print the current configuration. |
ward config edit |
Open the configuration in your editor. |
ward config path |
Print the path of the configuration file. |
Common options go after the subcommand: --category C,.., --org, --system, --repo and --format text|json. The global options are --config, --parallelism and -v. See the command reference.
A manifest names the organization, the systems and the categories to manage:
[org]
name = "my-github-org"
[[systems]]
id = "backend"
name = "Backend Services"
repos = ["backend-api", "backend-worker"]
[categories.repository.policy]
disposition = "managed"
prune = false
sensitive = false
[categories.repository.settings]
delete_branch_on_merge = true
topics = ["managed-by-ward"]Start from ward.example.toml or run ward import. The configuration reference describes every field.
Run ward drift on a schedule. The step fails when GitHub no longer matches ward.toml.
name: Ward drift
on:
schedule:
- cron: "0 8 * * 1"
workflow_dispatch:
jobs:
drift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install Ward
run: |
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/OriginalMHV/Ward/releases/latest/download/ward-cli-installer.sh | sh
- name: Check drift
env:
GH_TOKEN: ${{ secrets.WARD_TOKEN }}
run: ward drift --format json| Exit code | Meaning |
|---|---|
0 |
Every repository matches ward.toml. |
1 |
Drift found. This includes blocked or deferred changes and unreadable state in managed categories. |
2 |
Ward could not run the check. Authentication, network, config or arguments are wrong. |
JSON goes to stdout and progress goes to stderr. See CI integration for more patterns.
Compared on 2026-10-06 using each project's public README and documentation: Ward 0.5.0 docs, terraform-provider-github (README, registry and resource docs), safe-settings README, Terraform CLI and state docs. Probot Settings is left out because its README does not document these features. Partial means the project does part of the row or documents only part of it. "Config files through a pull request" means repository config files under .github/. "Resource breadth" covers rulesets, environments, access, Actions and webhooks.
Comparison as text
| Feature | Ward | Terraform GitHub provider | safe-settings |
|---|---|---|---|
| Preview before change | Yes | Yes | Yes |
| Drift check with CI exit codes | Yes | Yes | Partial |
| No state file to store or lock | Yes | No | Yes |
| Config files through a pull request | Yes | Partial | No |
| Enforces automatically on GitHub events | No | No | Yes |
| Import an existing repository | Yes | Partial | Yes |
| Resource breadth | Yes | Yes | Partial |
| Organization-level settings | Partial | Yes | Partial |
How we compared: Ward cells come from commands, architecture, GitHub coverage and CI integration. Competitor cells come from the Terraform provider, the Terraform state docs and the safe-settings README.
The Terraform provider has broader organization-level coverage. If you already run Terraform, it fits that workflow. safe-settings runs continuously and reacts to GitHub events. Ward is a CLI that you run on demand or in CI, and it needs no deployed service.
- No global apply-all.
ward applyworks one category at a time, in a fixed safe order, and shows the plan first. - Sensitive categories are gated. They need
sensitive = trueanddisposition = "managed"before Ward writes them. - Secret values stay out of Ward. Ward never reads secret values from GitHub, and never stores them in the manifest, the plan or the audit log. It resolves each value from your environment only when it writes.
- Audit log. Every change is appended to
~/.ward/audit.log. On Windows this is.ward\audit.login your user profile folder. - A missing list is never pruned. If
teamsorcollaboratorsis absent from the manifest, Ward leaves them alone. Only an explicit list withprune = trueremoves entries. - No repository lifecycle. Ward never creates, renames, transfers or deletes repositories. Visibility and archive changes need
--allow-high-impactorsensitive = trueon the repository category. - Files go through pull requests. Ward never pushes managed files to the default branch.
| Guide | Description |
|---|---|
| Getting started | Set up a manifest, review it, plan and apply |
| Commands | Full CLI reference |
| Configuration | Categories, policies, references and placeholders |
| GitHub coverage | Supported settings and API limits |
| Architecture | Collectors, planning, ordering and verification |
| CI integration | Run Ward in GitHub Actions |
| Changelog | Release notes and breaking changes |
Read CONTRIBUTING.md first. Before you open a pull request, run:
cargo fmt -- --check
cargo clippy --all-targets -- -D warnings
cargo test --all-targetsReport security issues as described in SECURITY.md.
MIT. See LICENSE.
