Files
rune/docs/forge-client.md
T
Nick fe9709c035 fix(failures): search_issues targets the real repo-scoped list endpoint (#122)
Gitea/Forgejo has no repo-scoped /issues/search path — every dedup
search 404'd on a real forge, so everything spooled and nothing ever
filed. Dedup now queries GET /issues?state=open&type=issues&q=<marker>
(paginated, server-side q) with a client-side marker contains-check on
title/body. FakeForge keeps mirroring the server contract (q matches
titles only) instead of the client's filter; docs updated.
2026-09-07 16:57:49 +01:00

2.4 KiB

Forge Client

Config-driven forge abstraction (issue #121): Rune's automation creates issues, comments, and reads state through a ForgeClient protocol with adapters for Gitea, Forgejo, or no forge at all. Nothing is hardcoded to a specific setup — this is the open-project readiness foundation and the prerequisite for the failure auto-filer (#122).

Components (src/rune/forge_client.py)

  • ForgeClient — @runtime_checkable Protocol: create_issue(title, body), add_comment(issue_number, body), get_issue_state(issue_number), list_open_issues(), search_issues(query) (#122)
  • NullAdapter — every method is a no-op dict; zero errors; the graceful dormant path for unconfigured/standalone use
  • GiteaForgejoAdapter — real HTTP adapter via Gitea/Forgejo /api/v1 paths (identical on both platforms); Authorization: token <token>
  • create_forge_client(config) — factory; unconfigured → NullAdapter

search_issues(query) (#122) is the repo-scoped marker search over the paginated LIST endpoint (GET /issues?state=open&type=issues&q=<query>

  • client-side marker contains-check on title/body). Gitea/Forgejo has NO repo-scoped /issues/search path — the failure auto-filer dedups through the list endpoint precisely because it is paginated and server-filterable.

Configuration

The factory checks a config dict first, then environment variables:

Key Env var Meaning
forge.url FORGE_URL Forge base URL, e.g. https://forge.example.com
forge.token FORGE_TOKEN API token
forge.owner FORGE_OWNER Repo owner
forge.repo FORGE_REPO Repo name

If URL or token is missing → NullAdapter (never raises).

# Standalone (no forge): nothing to do — NullAdapter is dormant.
# Gitea/Forgejo:
export FORGE_URL="https://forge.example.com"
export FORGE_TOKEN="..."
export FORGE_OWNER="o"   # optional
export FORGE_REPO="r"    # optional

API paths used (Gitea == Forgejo)

POST   {url}/api/v1/repos/{owner}/{repo}/issues
POST   {url}/api/v1/repos/{owner}/{repo}/issues/{n}/comments
GET    {url}/api/v1/repos/{owner}/{repo}/issues/{n}
GET    {url}/api/v1/repos/{owner}/{repo}/issues?state=open&limit=50
GET    {url}/api/v1/repos/{owner}/{repo}/issues?state=open&type=issues&q=<query>&limit=50&page=N

Run

PYTHONPATH=src:. python3 -m pytest tests/test_forge_client.py -q