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.
2.4 KiB
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_checkableProtocol: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 useGiteaForgejoAdapter— real HTTP adapter via Gitea/Forgejo/api/v1paths (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/searchpath — 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