refactor(server): migrate webhook-handlers to ForgePort — final Forgejo consumer #281

Merged
charles merged 1 commit from refactor/migrate-webhook-handlers-to-forgeport into main 2026-04-23 22:17:02 +00:00
Collaborator

Final pass at the Forgejo consumer migration kicked off by #274. After this lands, the only non-adapter importers of forgejo-api.ts left in the server are (a) deps.ts' acknowledged getIssueBlocks hold-out for the cross-repo filter (#196) and (b) webhook.test.ts for test-only coverage.

Port extensions

  • ForgePort.listPullRequests(repo, state, opts?: { baseRef?: string }) — matches Forgejo's server-side base=<ref> filter. Adapter routes to listOpenPullRequestsForBase when set, listOpenPullRequests otherwise.
  • ForgePort.repoHasWorkflows(repo): Promise<boolean> — no-CI fallback path needs this check; delegates to the existing api.repoHasWorkflows.

webhook-handlers.ts

8 call sites migrated (getIssue, listOpenPullRequests, listPullReviews, getPullRequest ×3, repoHasWorkflows, listOpenPullRequestsForBase).

  • findLinkedPrForIssue return shape flipped from raw {number, head: {sha,ref}, user: {login}} to domain {number, headSha, headRef, author} — internal helper only
  • latestVerdict signature migrated to ForgeReview[] + lowercase enum + commitSha; only consumer is same-file detectOutstandingChangeRequest
  • fetchLinkedIssueLabels reads issue?.labels directly (string[] now)
  • fetchPrDispatchLabels broadened to accept labels: ({name}|string)[] to bridge raw-payload and port-shaped inputs
  • handlePostMergeRebase reuses one ForgejoAdapter across the loop; reads pr.author / pr.headSha / detail.headSha|body|mergeable directly

Additional clean-up caught in review

Subagent review caught three sites missed in earlier PRs; migrated here for completeness:

  • pipeline.ts bounce-review handlergetPullRequest, deleteReviewRequest, requestReviewersforge.getPullRequest, forge.removeReviewRequest, forge.requestReview. Reads pr.requestedReviewers (string[]) directly instead of (pr.requested_reviewers ?? []).map(r => r.login).
  • board.ts drag-to-assignupdateIssueAssigneesforge.updateAssignees
  • board.ts reroute handlerupdateIssueAssignees + createIssueCommentforge.updateAssignees + forge.createComment (reused adapter)
  • board.ts top-of-file docstring — updated the dispatch-path note to reference ForgejoAdapter.updateAssignees

What stays raw (intentional)

  • Webhook event payloads (issues.*, pull_request.*, issue_comment, pull_request_review) — the port is for outbound API calls, not for bodies Forgejo sends us. pr.user.login, comment.user.login, pr.head?.ref, etc. continue to read from raw payloads.
  • labelNames() helper — still needed for raw-payload call sites (webhook-handlers.ts:135, 389, 435).
  • deps.ts::getIssueBlocks — the documented cross-repo filter hold-out from #277. Future work: add sourceRepo?: string to ForgeIssueRef and migrate.

Checks

  • bunx tsc --noEmit -p apps/server/tsconfig.json — EXIT=0
  • bun --cwd apps/server test — 1006 pass / 4 pre-existing fails
  • grep -rn "infrastructure/forge/forgejo-api" apps/server/src --include="*.ts" post-merge:
    • deps.ts — 1 documented hold-out
    • webhook.test.ts — 1 test-only
    • forgejo-adapter.ts — the implementation
    • forgejo-api.ts — the module itself

Follow-ups (not in this PR)

  • Audit forgejo-api.ts exports — most are now only reachable via the adapter; unused exports can be pruned
  • Phase 5a: split foreman.ts (prerequisite for agent-runner SDK migration)
  • Phase 5b: migrate agent-runner.ts to SdkClaudeAgent — biggest SDK-decoupling win
  • Unit tests for the 7 extracted s11 modules (PR #273 reviewer follow-up)
Final pass at the Forgejo consumer migration kicked off by #274. After this lands, the only non-adapter importers of `forgejo-api.ts` left in the server are (a) `deps.ts`' acknowledged `getIssueBlocks` hold-out for the cross-repo filter (#196) and (b) `webhook.test.ts` for test-only coverage. ## Port extensions - `ForgePort.listPullRequests(repo, state, opts?: { baseRef?: string })` — matches Forgejo's server-side `base=<ref>` filter. Adapter routes to `listOpenPullRequestsForBase` when set, `listOpenPullRequests` otherwise. - `ForgePort.repoHasWorkflows(repo): Promise<boolean>` — no-CI fallback path needs this check; delegates to the existing `api.repoHasWorkflows`. ## webhook-handlers.ts 8 call sites migrated (`getIssue`, `listOpenPullRequests`, `listPullReviews`, `getPullRequest` ×3, `repoHasWorkflows`, `listOpenPullRequestsForBase`). - `findLinkedPrForIssue` return shape flipped from raw `{number, head: {sha,ref}, user: {login}}` to domain `{number, headSha, headRef, author}` — internal helper only - `latestVerdict` signature migrated to `ForgeReview[]` + lowercase enum + `commitSha`; only consumer is same-file `detectOutstandingChangeRequest` - `fetchLinkedIssueLabels` reads `issue?.labels` directly (string[] now) - `fetchPrDispatchLabels` broadened to accept `labels: ({name}|string)[]` to bridge raw-payload and port-shaped inputs - `handlePostMergeRebase` reuses one `ForgejoAdapter` across the loop; reads `pr.author` / `pr.headSha` / `detail.headSha|body|mergeable` directly ## Additional clean-up caught in review Subagent review caught three sites missed in earlier PRs; migrated here for completeness: - **`pipeline.ts` bounce-review handler** — `getPullRequest`, `deleteReviewRequest`, `requestReviewers` → `forge.getPullRequest`, `forge.removeReviewRequest`, `forge.requestReview`. Reads `pr.requestedReviewers` (string[]) directly instead of `(pr.requested_reviewers ?? []).map(r => r.login)`. - **`board.ts` drag-to-assign** — `updateIssueAssignees` → `forge.updateAssignees` - **`board.ts` reroute handler** — `updateIssueAssignees` + `createIssueComment` → `forge.updateAssignees` + `forge.createComment` (reused adapter) - **`board.ts` top-of-file docstring** — updated the dispatch-path note to reference `ForgejoAdapter.updateAssignees` ## What stays raw (intentional) - **Webhook event payloads** (`issues.*`, `pull_request.*`, `issue_comment`, `pull_request_review`) — the port is for outbound API calls, not for bodies Forgejo sends us. `pr.user.login`, `comment.user.login`, `pr.head?.ref`, etc. continue to read from raw payloads. - **`labelNames()` helper** — still needed for raw-payload call sites (`webhook-handlers.ts:135, 389, 435`). - **`deps.ts::getIssueBlocks`** — the documented cross-repo filter hold-out from #277. Future work: add `sourceRepo?: string` to `ForgeIssueRef` and migrate. ## Checks - `bunx tsc --noEmit -p apps/server/tsconfig.json` — EXIT=0 - `bun --cwd apps/server test` — 1006 pass / 4 pre-existing fails - `grep -rn "infrastructure/forge/forgejo-api" apps/server/src --include="*.ts"` post-merge: - `deps.ts` — 1 documented hold-out - `webhook.test.ts` — 1 test-only - `forgejo-adapter.ts` — the implementation - `forgejo-api.ts` — the module itself ## Follow-ups (not in this PR) - Audit `forgejo-api.ts` exports — most are now only reachable via the adapter; unused exports can be pruned - Phase 5a: split `foreman.ts` (prerequisite for agent-runner SDK migration) - Phase 5b: migrate `agent-runner.ts` to `SdkClaudeAgent` — biggest SDK-decoupling win - Unit tests for the 7 extracted s11 modules (PR #273 reviewer follow-up)
refactor(server): migrate webhook-handlers to ForgePort — final Forgejo consumer
All checks were successful
qa / qa (pull_request) Successful in 4m9s
qa / dockerfile (pull_request) Successful in 35s
b77d93bdad
Last of the Forgejo consumer migrations. Completes the per-consumer
port adoption kicked off by PR #274's hexagonal scaffolding. No direct
forgejo-api imports remain in http/domain/background/background code.

## Port extensions

- `ForgePort.listPullRequests` accepts optional `{ baseRef?: string }` —
  matches Forgejo's server-side `base=<ref>` filter on `/pulls`. The
  adapter routes to `listOpenPullRequestsForBase` when the opt is
  present, `listOpenPullRequests` otherwise.
- `ForgePort.repoHasWorkflows(repo): Promise<boolean>` — one-shot
  workflow presence check used by the no-CI fallback path. Adapter
  delegates to the existing `api.repoHasWorkflows` helper.

## webhook-handlers.ts changes

- 8 Forgejo API call sites migrated: `getIssue`, `listOpenPullRequests`,
  `listPullReviews`, `getPullRequest` ×3, `repoHasWorkflows`,
  `listOpenPullRequestsForBase`
- `findLinkedPrForIssue` return shape switched from raw Forgejo
  `{ number, head: { sha, ref }, user: { login } }` to a domain-shaped
  `{ number, headSha, headRef, author }` — internal helper, one caller
- `latestVerdict` signature migrated to `ForgeReview[]` input + lowercase
  state enum output (`"approved" | "changes_requested"`) with `commitSha`
  instead of `commit_id`. `detectOutstandingChangeRequest` updated to
  match. Same-file helper only; no external callers.
- `fetchLinkedIssueLabels` reads `issue?.labels` directly (string[] now);
  dropped the `labelNames()` wrapper on that one call site.
- `fetchPrDispatchLabels` broadened to accept `labels: ({name}|string)[]`
  so it handles both raw webhook payload (event bodies stay raw) and
  port-shaped PR details.
- `handlePostMergeRebase` loop now uses one `ForgejoAdapter` for the
  whole iteration; reads `pr.author` / `pr.headSha` / `detail.headSha`
  / `detail.body` / `detail.mergeable` directly.

## What stays raw (intentional)

- Webhook **event payloads** (`issues.*`, `pull_request.*`, `issue_comment`,
  `pull_request_review`) — the port is for outbound API calls, not for
  webhook bodies Forgejo sends us. `pr.user.login`, `comment.user.login`,
  `pr.head?.ref`, etc. continue to read from the raw payload.
- `labelNames()` helper — still needed for the raw-payload call sites at
  lines ~135, 389, 435 where label arrays come from webhook events.

## Tests

- `latestVerdict` test suite rewritten to construct `ForgeReview` shapes
  via a small `rv()` helper (same pattern as review-loop.test.ts)
- 52/52 webhook-handlers tests pass
- Full server suite: 1006 pass / 4 pre-existing fails

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
charles force-pushed refactor/migrate-webhook-handlers-to-forgeport from b77d93bdad
All checks were successful
qa / qa (pull_request) Successful in 4m9s
qa / dockerfile (pull_request) Successful in 35s
to 56e4b6b678
All checks were successful
qa / qa (pull_request) Successful in 4m14s
qa / dockerfile (pull_request) Successful in 8s
2026-04-23 22:15:10 +00:00
Compare
charles deleted branch refactor/migrate-webhook-handlers-to-forgeport 2026-04-23 22:17:02 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!281
No description provided.