Runs the suiflex engineering standard across the organization's repositories.
ForgeGuard is excellent locally and runs only when someone remembers to run it. Guardener is the same analysis, applied to every pull request in the organization whether anyone remembers or not, from one policy kept in one place.
guardener check runs ForgeGuard's gate over the lines a pull request
changed and reports the result twice: as a check run whose annotations land on
the offending lines, and as a single comment that is edited in place on every
push and removed once the findings are gone.
guardener hygiene walks every watched repository and reports what has
drifted from the organization standard — a missing licence, owners file, triage
or gate workflow, a label the triage workflow needs and cannot find, an
unprotected default branch, a third-party action named by a tag rather than a
commit. It writes one issue on this repository, edited in place and closed once
there is nothing left to say.
--fix opens a pull request adding the files a repository is missing. It only
ever adds: it never edits or replaces a file that already exists, and it never
changes a setting. Labels and branch protection are reported and left to a
person. That boundary is the point — a bot that quietly revises decisions across
an organization is worse than the drift it was meant to catch.
The daily sweep never passes --fix; opening pull requests across the
organization is something a person triggers from workflow_dispatch.
Guardener acts as its own GitHub App, guardener-bot, rather than borrowing the
one that labels pull requests. The two need very different things: triage reads
team membership and writes labels, while this writes check runs, opens pull
requests and reads branch protection. Sharing one app would mean granting the
label bot the right to commit to every repository in the organization.
Its credentials reach the workflows as the organization secrets
GUARDENER_BOT_APP_ID and GUARDENER_BOT_PRIVATE_KEY. Every repository that
calls the gate needs them, so they have to be visible to it — an organization
secret restricted to selected repositories that omits one is the failure this
is most often traced back to. Check with:
gh api /repos/suiflex/<repo>/actions/organization-secrets --jq '.secrets[].name'The app needs, and needs no more than:
| Repository permission | Why |
|---|---|
| Metadata: read | Required of every app |
| Contents: read and write | Read a repository's tree and workflows; write the branch and files a --fix pull request carries |
| Checks: read and write | Report the gate result on a pull request |
| Issues: read and write | Read the label set; keep the standing hygiene issue |
| Pull requests: read and write | The gate's comment, and opening a --fix pull request |
| Administration: read | Read branch protection, and nothing else — drop it and exempt = ["branch-protection"] if that is too much to grant |
No organization permissions, and no webhook: nothing here listens, so the app
should have its webhook switched off. Each workflow narrows further than this
through permission-* on the token it mints, so a mistake in one workflow
cannot reach what the others use.
config/forgeguard.toml is the ForgeGuard configuration used by any repository
that does not ship its own. A repository that keeps a .forgeguard/config.toml
overrides it completely — the organization default is a floor, not a ceiling.
config/repositories.toml lists the watched repositories: the public ones, and
not homebrew-tap or scoop-bucket, whose contents a release workflow writes
rather than a person. One line each is the normal case; mode, rules and
exempt are there for the repository that genuinely needs an exception, so the
exception is visible centrally instead of buried.
config/labels.toml is the label set the organization's triage workflow
applies. suiflex/.github/scripts/sync-labels.sh creates those labels; this
file is how Guardener audits that it was run.
Being listed in the registry puts a repository in the hygiene sweep. The gate is separate and opt-in: it runs only where the repository has added the stub below.
The policy is strict, which on ForgeGuard's version 2 blocks every finding at
warning severity or above rather than errors alone. That is bearable only
because the gate reads the lines a pull request changed and nothing else: it
blocks on work already in front of the author, never on the history behind it.
A repository that needs a softer gate says so with its own mode in the
registry.
Setting all of this up — the app, its permissions, the secrets, the model — is in INSTALL.md.
Add this to the repository as .github/workflows/guardener.yml — or let
guardener hygiene --fix open the pull request that adds it:
name: Guardener
on:
pull_request_target:
types: [opened, reopened, synchronize]
jobs:
forgeguard:
if: github.repository_owner == 'suiflex'
uses: suiflex/Guardener/.github/workflows/check.yml@main
secrets:
app_id: ${{ secrets.GUARDENER_BOT_APP_ID }}
private_key: ${{ secrets.GUARDENER_BOT_PRIVATE_KEY }}make lists what there is. make verify is what CI runs, in CI's order.
make hygiene and make hygiene-fix preview the sweep and the pull requests it
would open, writing nothing.
--dry-run prints the requests instead of sending them. It still reads — it
has to know what is already there for the preview to be worth anything — so it
needs a token, and a read-only personal one will do:
GUARDENER_TOKEN=$(gh auth token) cargo run -- check \
--root ../ForgeGuard \
--repo suiflex/ForgeGuard \
--pr 123 --head-sha "$(git -C ../ForgeGuard rev-parse HEAD)" \
--base origin/main \
--dry-runThe hygiene sweep reads the whole organization, so a dry run is the way to see what it would say and what it would open, without writing anything:
GUARDENER_TOKEN=$(gh auth token) cargo run -- hygiene --dry-runA real run reads its token from GUARDENER_TOKEN.
assets/brand/ holds the mark, the wordmark in both themes, and
app-icon.png — the 512×512 square uploaded as the GitHub App's avatar.
The shield is ForgeGuard's, because this enforces ForgeGuard's standard. It is drawn as an outline rather than filled so the two are told apart at the twenty pixels a bot avatar actually gets beside a comment, and the eye at its centre says what this one does that the other does not: it watches, without being asked.
guardener review has a model read the pull request and say what a scanner
cannot: logic that is wrong, an invariant the change breaks, an error path that
loses data quietly, a contract its callers will not survive. It is told what
ForgeGuard already reports so it does not spend the budget re-deriving findings
that arrive with line numbers attached, and it is asked to return an empty
answer when nothing meets that bar — which is the expected answer for most pull
requests.
It is deliberately unequal to the gate. It posts under its own marker, it never creates a check run, and it runs in a workflow of its own, so a model that is slow, wrong or unreachable can never be the reason a pull request cannot merge.
It is also never asked on its own initiative. Two things can start it: a
/review comment, and the weekly sweep below. It deliberately does not run on
every push — a reading nobody asked for, repeated on every commit of every pull
request in the organization, paid for far more of them than anyone read.
It reads the diff from the API rather than a checkout, so unlike the gate it needs no clone.
Three environment variables carry the endpoint, and none of them are in this
repository — together they name a private service, so they arrive as the
secrets GUARDENER_MODEL_URL, GUARDENER_MODEL_KEY and GUARDENER_MODEL.
Changing which model reviews pull requests is a change to a secret, not a pull
request against this repository. The endpoint is expected to answer at
<url>/chat/completions in the shape OpenAI made common, which is also what
Ollama, vLLM and most gateways speak.
config/review.toml holds what is worth arguing about instead: how large a
change is still worth reading, and which files carry no judgement worth paying
for.
A repository that passes no model_url to the workflow still gets the gate;
it just gets no second opinion.
Comment /review on a pull request and the model reads it again. That runs
.github/workflows/review.yml — the review on its own, not the gate a second
time: the review reads the diff from the API, so it needs neither the checkout
nor the merge base the gate cannot do without.
Only the owner, an organization member, or an invited collaborator can ask.
issue_comment runs in the base repository's context with a writable token, so
without that check anyone able to type in a comment box could spend the model
budget. guardener hygiene --fix opens the pull request that adds the stub to a
repository missing it.
guardener review with no --pr walks every watched repository for open pull
requests that carry no review comment at all, and reviews them. Since nothing
reviews on every push any more, this is what catches a pull request whose author
never thought to type /review — and the "no comment at all" condition is what
stops it re-reading the ones already answered.
--stale-days defaults to 0, so age is no bar: a pull request opened this
morning is as eligible as one from last month. Raise it to leave recent work
alone. --max (default 30) bounds a run, because the first one meets every
never-reviewed pull request at once. --repo narrows the walk to one
repository, the way hygiene --repo does.
A dry run lists the candidates and asks the model nothing, unlike a dry run of a single review, where the model's answer is the thing worth previewing:
GUARDENER_TOKEN=$(gh auth token) cargo run -- review --dry-run.github/workflows/review-sweep.yml runs it weekly, on a Monday morning, and
takes stale_days, max and dry_run from workflow_dispatch.
Inline review comments. The model already reports a file and a line, but they are rendered as text: placing a remark on the wrong line is worse than placing it in a list, and the diff-position arithmetic that avoids that has not been written.