MF-2: GitLab adapter for ForgePort #293

Closed
opened 2026-04-23 23:33:11 +00:00 by code-lead · 0 comments
Collaborator

As a platform engineer, I want a GitLab implementation of ForgePort (REST v4, gitlab.com or self-hosted) so that GitLab-hosted repos route through the same domain pipeline as Forgejo and GitHub.

Acceptance criteria

Adapter surface

  • New apps/server/src/infrastructure/forge/gitlab-adapter.ts.
  • Auth via per-agent GitLab PAT (scope: api). Project identifier = URL-encoded owner/name.
  • Base URL configurable per repo (gitlab.com by default; honour self-hosted via config).

Issue / MR / note mapping

  • ForgeIssue/projects/{id}/issues/{iid}.
  • ForgePullRequest/merge_requests/{iid}:
    • baseRef = target_branch
    • headRef = source_branch
    • headSha = sha
  • ForgeComment/notes.

Dependencies (probe + fallback)

  • GitLab Premium exposes issue_links with relates_to | blocks | is_blocked_by types. Free does not.
  • Probe once at startup (cache per session); if Premium, use native. If Free, fall back to body-parse (same parser as the GitHub adapter in MF-1).
  • Re-probe on error. Document behaviour in adapter header.

Reviews (approvals mapping)

  • GitLab uses MR approvals + discussions, not GitHub-style reviews.
  • Mapping:
    • approval_stateapproved
    • emoji reaction thumbsdown on a discussion → changes_requested
    • plain note → comment
  • listReviews returns the merged stream in that shape.
  • submitReview writes to the matching surface (approval for APPROVED, note + thumbsdown for REQUEST_CHANGES).

CI / pipelines

  • getAggregateStatus / listWorkflowRuns: /projects/{id}/pipelines/{sha}.
  • getJobLogs: /jobs/{jobId}/trace.

Repo scope

  • listAccessibleRepos: /projects?membership=true.

Tests

  • Cross-adapter conformance suite (MF-6) passes against the GitLab adapter using recorded fixtures.
  • Approvals-as-reviews mapping verified with fixtures covering approved, thumbsdown, note.
  • Premium-vs-Free probe has both-branch fixtures.

Out of scope

  • GitLab merge trains — treat as regular merge; this service uses squash.
  • discussions threading beyond top-level notes.
  • GraphQL surface — REST v4 is sufficient.
  • Mirroring GitLab webhooks — that is MF-3.

References

  • Spec: specs/multi-forge.md § MF-2.
  • apps/server/src/infrastructure/forge/forgejo-port.ts — port surface.
  • MF-1 GitHub adapter — pattern to follow.

Dependencies

  • Blocks on MF-4 (config schema + adapter factory).
  • Blocks on MF-6 (conformance harness) — GitLab is the second non-Forgejo adapter, so the harness from MF-1/MF-6 is assumed to exist and wired into just qa by the time this lands.
  • Depends on #295 (MF-4)
  • Depends on #297 (MF-6)
As a platform engineer, I want a GitLab implementation of `ForgePort` (REST v4, `gitlab.com` or self-hosted) so that GitLab-hosted repos route through the same domain pipeline as Forgejo and GitHub. ## Acceptance criteria ### Adapter surface - [ ] New `apps/server/src/infrastructure/forge/gitlab-adapter.ts`. - [ ] Auth via per-agent GitLab PAT (scope: `api`). Project identifier = URL-encoded `owner/name`. - [ ] Base URL configurable per repo (`gitlab.com` by default; honour self-hosted via config). ### Issue / MR / note mapping - [ ] `ForgeIssue` ← `/projects/{id}/issues/{iid}`. - [ ] `ForgePullRequest` ← `/merge_requests/{iid}`: - `baseRef` = `target_branch` - `headRef` = `source_branch` - `headSha` = `sha` - [ ] `ForgeComment` ← `/notes`. ### Dependencies (probe + fallback) - [ ] GitLab Premium exposes `issue_links` with `relates_to | blocks | is_blocked_by` types. Free does not. - [ ] Probe once at startup (cache per session); if Premium, use native. If Free, fall back to body-parse (same parser as the GitHub adapter in MF-1). - [ ] Re-probe on error. Document behaviour in adapter header. ### Reviews (approvals mapping) - [ ] GitLab uses MR approvals + discussions, not GitHub-style reviews. - [ ] Mapping: - `approval_state` → `approved` - emoji reaction `thumbsdown` on a discussion → `changes_requested` - plain note → `comment` - [ ] `listReviews` returns the merged stream in that shape. - [ ] `submitReview` writes to the matching surface (approval for APPROVED, note + thumbsdown for REQUEST_CHANGES). ### CI / pipelines - [ ] `getAggregateStatus` / `listWorkflowRuns`: `/projects/{id}/pipelines/{sha}`. - [ ] `getJobLogs`: `/jobs/{jobId}/trace`. ### Repo scope - [ ] `listAccessibleRepos`: `/projects?membership=true`. ### Tests - [ ] Cross-adapter conformance suite (MF-6) passes against the GitLab adapter using recorded fixtures. - [ ] Approvals-as-reviews mapping verified with fixtures covering `approved`, `thumbsdown`, `note`. - [ ] Premium-vs-Free probe has both-branch fixtures. ## Out of scope - GitLab merge trains — treat as regular merge; this service uses squash. - `discussions` threading beyond top-level notes. - GraphQL surface — REST v4 is sufficient. - Mirroring GitLab webhooks — that is MF-3. ## References - Spec: [`specs/multi-forge.md`](../src/branch/main/specs/multi-forge.md) § MF-2. - `apps/server/src/infrastructure/forge/forgejo-port.ts` — port surface. - MF-1 GitHub adapter — pattern to follow. ## Dependencies - **Blocks on MF-4** (config schema + adapter factory). - **Blocks on MF-6** (conformance harness) — GitLab is the second non-Forgejo adapter, so the harness from MF-1/MF-6 is assumed to exist and wired into `just qa` by the time this lands. <!-- machine-parseable deps for the deps.ts body fallback; native POST /dependencies is currently returning HTTP 404 on this Forgejo. --> - Depends on #295 (MF-4) - Depends on #297 (MF-6)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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#293
No description provided.