feat(forge): GitLab adapter client and core Forge methods #144

Merged
deadstyle merged 7 commits from feature/138-gitlab-adapter-core into develop 2026-10-12 08:26:52 -05:00
Owner

Refs #138 (parent #137)

Summary

New internal/forge/gitlab adapter: the API client plus every core Forge method and Approver, so a GitLab session (gitlab.com or self-managed, subgroups included) can list projects, show MRs and issues with CI state, merge, approve, comment and close. ADR 0018 records the decisions; docs/architecture.md's GitLab column is updated, every row marked unverified (no live GitLab was used; fixtures are synthesised from docs.gitlab.com). Connecting from onboarding is #141.

Decisions

  • Thin net/http client, PRIVATE-TOKEN, API root <web root>/api/v4; projects addressed by URL-encoded full path (%2F kept on the request line).
  • No ETag cache: GitLab doesn't document REST 304s. CI comes from one pipelines list per project matched on head SHA; only open MRs with no match cost one extra GET. A 403/404 on the pipelines list (CI disabled) counts as no CI.
  • Merge reads the project once for squash_option; the method is the project's own (GitLab has no per-call method), and a different Method is ErrUnsupported.
  • 401 on merge/approve is ErrRefused (GitLab's "not allowed"), not a bad token. 429 waits Retry-After (≤ 60 s) and retries once.
  • Rulings during the run: Approve sends no sha (the interface carries no confirmed head); draft MRs aren't decoded (no domain field), so a draft merge fails with GitLab's 405 reason.

Test plan

  • make build, make test (go test -race ./..., 14/14 packages ok), make fmt-check, make vuln, make install-test, make vet-windows: pass.
  • Lint with the CI-pinned golangci-lint v2.14.0: 0 issues.
  • forgetest.RunContract against a stateful fake GitLab, plus unit tests for each plan item. Tests were written red-first from the acceptance criteria, then a mutation check: 100 behaviour mutations, all caught (3 hollow tests found and fixed).

🤖 Generated with Claude Code

Refs #138 (parent #137) ## Summary New `internal/forge/gitlab` adapter: the API client plus every core `Forge` method and `Approver`, so a GitLab session (gitlab.com or self-managed, subgroups included) can list projects, show MRs and issues with CI state, merge, approve, comment and close. ADR 0018 records the decisions; `docs/architecture.md`'s GitLab column is updated, every row marked unverified (no live GitLab was used; fixtures are synthesised from docs.gitlab.com). Connecting from onboarding is #141. ## Decisions - Thin `net/http` client, `PRIVATE-TOKEN`, API root `<web root>/api/v4`; projects addressed by URL-encoded full path (`%2F` kept on the request line). - No ETag cache: GitLab doesn't document REST 304s. CI comes from one pipelines list per project matched on head SHA; only open MRs with no match cost one extra GET. A 403/404 on the pipelines list (CI disabled) counts as no CI. - Merge reads the project once for `squash_option`; the method is the project's own (GitLab has no per-call method), and a different `Method` is `ErrUnsupported`. - 401 on merge/approve is `ErrRefused` (GitLab's "not allowed"), not a bad token. 429 waits `Retry-After` (≤ 60 s) and retries once. - Rulings during the run: Approve sends no `sha` (the interface carries no confirmed head); draft MRs aren't decoded (no domain field), so a draft merge fails with GitLab's 405 reason. ## Test plan - `make build`, `make test` (`go test -race ./...`, 14/14 packages ok), `make fmt-check`, `make vuln`, `make install-test`, `make vet-windows`: pass. - Lint with the CI-pinned golangci-lint v2.14.0: 0 issues. - `forgetest.RunContract` against a stateful fake GitLab, plus unit tests for each plan item. Tests were written red-first from the acceptance criteria, then a mutation check: 100 behaviour mutations, all caught (3 hollow tests found and fixed). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Refs #138

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs #138

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Red-first tests for the gitlab adapter: a stateful fake GitLab backing
forgetest.RunContract, plus tests for the CI fold, sha matching, error
mapping, 429 retry, paging, subgroup path encoding, access fallback,
system-note filtering, squash rule, Probe and Info. Fixtures are
synthesised from docs.gitlab.com examples, not recorded.

Refs #138

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs #138

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ListChangeRequests treats a 403 or 404 on the pipelines listing as no
pipelines, and runs the head_pipeline fallback only for open MRs.
ADR 0018 records both.

Refs #138

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs #138

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test(forge): prove the off-origin Link, sort and user-token cases can fail
All checks were successful
ci / check (pull_request) Successful in 1m16s
5c97d98d1f
The off-origin test passed on a DNS failure and the sort test on an
already sorted fixture. Both now fail when the guard is removed.

Refs #138

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
deadstyle merged commit ddb23b08ce into develop 2026-10-12 08:26:52 -05:00
deadstyle deleted branch feature/138-gitlab-adapter-core 2026-10-12 08:26:52 -05:00
Sign in to join this conversation.
No description provided.