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.
58 lines
2.4 KiB
Markdown
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
|