name: forge-host
description: Make governance components forge-host-aware (GitHub or self-hosted Forgejo/Gitea) instead of GitHub-only. Ships a thin shell adapter (forge-lib.sh) that detects the host per-repo and exposes host-agnostic forge_* operations (issues, comments, releases/tags, CI status) backed by gh for GitHub and curl+REST for Forgejo. Additive and backward-compatible: a repo with no Forgejo config behaves exactly as before. Use when a project is migrating repos from GitHub to a self-hosted Forgejo, when a component shells out to gh but the repo may be on Forgejo, or when you need deterministic per-repo host detection.
forge-host: host-aware forge operations
Several forge-kit components assume GitHub: they shell out to gh, hit api.github.com, read
.github/workflows/, or carry a {{GITHUB_REPO}} placeholder. When a repo moves to a self-hosted
Forgejo (or Gitea) instance, those stop working. This skill makes the forge operations
host-aware behind one adapter, so the same governance logic runs on either host.
One adapter, not two skills. Closing an issue, cutting a release, or checking CI is the same
contract with a different transport, an adapter concern. A single forge-lib.sh (the analogue
of release-automation's version-lib.sh) keeps the contract in one place; per-host quirks live
in references/, not a second skill.
The model
- Detect the host per-repo (never assume GitHub), deterministically, so it works unattended in
CI/hooks, not just interactively.
- Abstract the operations the components need (issues, comments, releases/tags, CI status)
behind
forge_* functions with two backends: GitHub (gh) and Forgejo (curl + REST).
- Additive: a repo with no
.forge.conf defaults to GitHub and behaves exactly as today.
Both backends speak REST (GitHub via gh api, Forgejo via curl), not gh's porcelain,
because Forgejo's API is the Gitea API, whose JSON shapes for issues/releases/comments closely
match GitHub's REST, so callers' jq parsing stays identical across hosts.
Host detection (first match wins, deterministic)
$FORGE_HOST env var: explicit override (e.g. in CI).
- A committed
.forge.conf at the repo root (assets/forge.conf.example).
- The git remote URL:
github.com → github; otherwise forgejo iff a Forgejo API URL is
configured, else github.
The committed .forge.conf is the canonical answer for a repo that has both remotes during
migration: detection must not "ask" in automation. A GitHub-only repo needs no config.
The adapter (assets/forge-lib.sh)
Source it; call forge_* instead of gh directly:
| Function | Purpose |
|---|
forge_host / forge_repo / forge_api_base | detection + identity |
forge_api <METHOD> <path> [body] | authenticated REST call (the low-level primitive) |
forge_issue_view <n> / forge_issue_list [state] | read issues |
forge_issue_comment <n> <body> / forge_issue_close <n> | act on issues |
forge_issue_create <title> <body> | open an issue (labels omitted, added with the next op) |
forge_issue_label <n> <name…> | add labels by name (resolves names→IDs on Forgejo) |
forge_tag_exists <tag> / forge_release_create <tag> [title] [notes] | releases/tags |
forge_ci_status <branch> | success|failure|pending|none|not_configured (Forgejo via the combined commit-status API; github via gh run list, also passing raw GH conclusions like cancelled through) |
FORGE_DRY_RUN=1 prints would-be requests (to stderr) instead of sending them. Run
bash forge-lib.sh detect for a one-line host/repo/api/ci diagnostic.
CI status degrades gracefully. On Forgejo with no runner yet, forge_ci_status returns
not_configured (rather than failing), so a caller can fall back to a local gate (e.g. make test) instead of hard-failing. The Forgejo branch is implemented via the combined commit-status
endpoint (/commits/{sha}/status): Forgejo Actions writes a commit status per job, so one call
yields success/failure/pending, and total_count: 0 (no statuses, e.g. no runner) →
not_configured. Live-verified end to end: a real runner executed a .forgejo/workflows/
job and the combined status flipped pending then success, with forge_ci_status returning
success for both SHA and branch refs (references/forgejo-ci.md). GitHub's combined status
does NOT reflect Actions (those are Checks), so the github path stays on gh run list.
Install (what forge-adapt does)
- Copy
assets/forge-lib.sh into the project (e.g. scripts/forge-lib.sh), chmod +x.
- For a Forgejo repo (or a dual-remote repo mid-migration), copy
assets/forge.conf.example
to .forge.conf, fill it in, and commit it. Export the token in the runtime env (never commit
it). A GitHub-only repo needs neither.
- Components adopt the adapter by replacing direct
gh calls with forge_*; see
references/adopting-forge-lib.md for the per-component swaps.
Supplying the token locally
.forge.conf names the env var (FORGE_TOKEN_ENV, default FORGEJO_TOKEN); how the token
gets INTO it depends on the context, detailed in references/local-auth.md:
- Humans: store the PAT in the OS keyring or
pass, surface it via shell profile or a
gitignored direnv .envrc (export FORGEJO_TOKEN=$(secret-tool lookup ...)).
- Claude Code sessions: a settings
env block reaches every Bash call and hook the
session spawns: ~/.claude/settings.json (user-global, set once for ALL projects on the
machine; the right default for one-instance setups) or the auto-gitignored
.claude/settings.local.json (per-project override, e.g. a narrower-scoped token).
- CI: a repository/org Actions secret mapped to the env var in the workflow.
- Fallback (built in): if the env var is empty,
_forge_token asks git's credential
helper for the instance host (git credential fill, non-interactive, read-only) before
erroring, so a machine whose git push already works over HTTPS needs no extra setup.
Mint the most restrictive token that works (write:issue, write:repository, repo-scoped);
Forgejo PATs never expire, so rotate manually. Don't use fj/tea config files as the
canonical store (both are plaintext).
Scope (phase 1 vs later)
This skill is the foundation: detection + the marker + the adapter contract, with the
runner-free operations (issues, comments, releases/tags) implemented for both hosts. Adopting it
across the GitHub-coupled components (ci-health, release/release-automation, ticket-gate,
gate-ticket, dep-auditor, health-check, forge-adapt's templates mode) is the follow-on
work; the CI/Actions backend (and porting release-automation's GitHub-App-token lanes) is gated
on a Forgejo runner, designed in references/forgejo-ci.md. See references/adopting-forge-lib.md
for the per-component swaps. (The invoked release skill is already host-aware for its tag/release/
ticket-close steps; only its remote-CI-green check is runner-gated and degrades to not_configured.)
Notes for forge-adapt
- Detect the host from the remote during analysis; if a non-GitHub remote has no
.forge.conf,
offer to create one (don't silently assume GitHub for a clearly-Forgejo remote).
- Preserve the
<!-- forge-host-version: N --> marker when adapting.
forge-lib.sh is stack-agnostic: copy it verbatim like a hook, don't rewrite it.