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

58 lines
2.4 KiB
Markdown

# 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