Forge support

Every forge behind one contract: PR enrichment (open/closed/merged state per branch) and report-issue (find/create/update one issue with a fixed title). Merge detection itself is fully offline — forge state is a best-effort enrichment on top.

Provider matrix

ForgeDetected remotePR backendreport-issue
GitHubgithub.comgh CLI, else REST + GITHUB_TOKEN
GitLabgitlab.com, *.gitlab.* self-hostedREST + GITLAB_TOKEN (or CI_JOB_TOKEN in GitLab CI)
Gitea / Codeberg / Forgejogitea.com, codeberg.org, forgejo.orgREST + GITEA_TOKEN (GitHub-compatible API)
Bitbucket Cloudbitbucket.orgREST + BITBUCKET_TOKEN
Bitbucket Server / Data Centerhost containing bitbucket, not bitbucket.orgREST (/rest/api/ dialect) + BITBUCKET_TOKEN❌ (issues live in Jira — loud error, never silent)

Self-hosted instances

GitLab self-hosted domains (*.gitlab.*) and Bitbucket Server hosts are recognized by hostname. For anything else — a Gitea at git.internal, a custom GitLab domain — claim the host explicitly; an explicit mapping always wins:

// .gitcleanup.json or ~/.config/git-cleanup/config.json
{
  "forge": {
    "hosts": {
      "git.example.com": "gitlab",
      "git.internal": "gitea",
      "stash.internal": "bitbucket-server"
    }
  }
}

Override API bases per forge when the instance lives behind a proxy or custom path: GITLAB_API_BASE, BITBUCKET_API_BASE, GITEA_API_BASE, GITHUB_API_BASE.

Tokens

Missing tokens never break scans: PR enrichment degrades to pure-Git detection, and git-cleanup doctor tells you exactly what to set.

Adding a forge

A provider implements one contract (see src/forge.mjs): PR shape { number, title, url, headRef, isDraft, state, updatedAt, mergedAt, ageDays } plus an optional issues capability. All consumers (analyze, classify, report, CLI, the GitHub Action) read only that shape, so a new forge is one provider module + one registration line.