### Added - `lib/forge.sh` — the forge selector: `forge_detect` names the forge from the runner's own environment, `forge_client` names the client it needs, and `forge_preflight` refuses loudly before any sweep when the two disagree (#188). - The reconcilers and `labels-scope` run that preflight first, so a GitHub-shaped client on a Forgejo instance is a named refusal instead of a sweep that reads nothing and reports success (#188). - `lib/closes_references.sh` — the closing-keyword parser, sibling of `refs_references`, so "which issues does this PR close" is answered from a PR body rather than from GitHub's GraphQL API (#188). - `lib/forge-github.sh` and `lib/forge-forgejo.sh` — one call surface, two backends, selected by `forge_select`; no forge branching at the call sites (#188). - The forgejo backend proves each paginated gather complete against the server's `x-total-count` and refuses loudly when it cannot — a missing header is a refusal, not a pass (#188). ### Changed - `issueflow-reconcile` gathers open and merged PRs over REST instead of `gh api graphql`. Forgejo serves no GraphQL at all, so the two queries were replaced rather than translated; both forges return `number` and `body` from `/pulls` in the same shape (#188). - `forge_api` owns the page size, because each forge silently ignores the other's parameter: `per_page=100` reads 30 items on Forgejo and `limit=100` reads 30 on GitHub, both HTTP 200. No call site names one (#188). ### Fixed - `labels-reconcile` and `labels-scope` no longer exit 0 on a Forgejo consumer having read zero facts — measured on `heavy-duty/rig`, where the sweep printed `reconciled.` over an empty PR list and scope reported "no labeler.yml" for a file that exists (#188).