Skip to content

feat(gitlab): issue and merge request writeback parity - #282

Merged
khaliqgant merged 3 commits into
mainfrom
feat/gitlab-writeback-parity
Sep 19, 2026
Merged

khaliqgant merged 3 commits into
mainfrom
feat/gitlab-writeback-parity

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Sep 19, 2026 •

Copy link
Copy Markdown
Member

GitLab writeback parity for issues and merge requests

Problem

GitLab is advertised as a first-class Relayfile provider, but its writeback catalog previously exposed only merge-request discussions and issue notes. A GitLab-backed software factory could not create issues or merge requests through Relayfile.

Catalog

Before After
comments, discussions issues (create + update), merge-requests (create + update), refs, merge, close-merge-request, plus the existing comments and discussions

New canonical writeback templates:

  • /gitlab/projects/{projectPath}/issues
  • /gitlab/projects/{projectPath}/issues/{issueIid}__{slug}/meta.json
  • /gitlab/projects/{projectPath}/merge-requests
  • /gitlab/projects/{projectPath}/merge_requests/{mergeRequestIid}__{slug}/meta.json
  • /gitlab/projects/{projectPath}/merge_requests/{mergeRequestIid}__{slug}/merge.json
  • /gitlab/projects/{projectPath}/merge_requests/{mergeRequestIid}__{slug}/close.json
  • /gitlab/projects/{projectPath}/refs

GitLab REST v4 endpoints

  • POST /projects/:id/issues
  • PUT /projects/:id/issues/:iid
  • POST /projects/:id/merge_requests
  • PUT /projects/:id/merge_requests/:iid
  • PUT /projects/:id/merge_requests/:iid/merge
  • PUT /projects/:id/merge_requests/:iid with state_event: close | reopen
  • POST /projects/:id/repository/branches

Payload validation rejects missing required fields, invalid lifecycle states, read-only fields, unknown fields, and unsupported paths. Label arrays are normalized to GitLab's comma-separated API representation.

Discovery and layout

Every new resource has non-empty JSON Schema and create-example contracts, is represented in the GitLab adapter discovery guide, layout manifest, LAYOUT prompt, mappings, and generated writeback catalog. The shared file-native router now supports nested GitLab projectPath segments when recognizing exact writeback sidecars.

Validation

  • npm test -w @relayfile/adapter-gitlab — 93 passing
  • npm run typecheck -w @relayfile/adapter-gitlab
  • npm run build -w @relayfile/adapter-gitlab
  • npm test (workspace gate)
  • npm run typecheck (workspace gate)
  • npm run test:writeback-discovery — 119 endpoints verified
  • npm run test:digest-contracts
  • Veto diff review — PASS (one low-severity follow-up: restore explicit TypeScript payload/request types in writeback.ts)

Scope

Nothing was skipped. refs is included because it is the GitLab equivalent of GitHub's branch-creation route and lets a flow create a branch before opening a merge request. f.gitlab was not changed.

Release note

@relayfile/adapter-gitlab is bumped from 0.4.2 to 0.5.0 for the new capability. Consumers that mount GitLab paths should resync to materialize the new discovery and catalog surface.

Reviewer notes (orchestrator)

  • Rebased onto main after relayfile-adapters#281 / the 0.2.1 publish; re-ran npm test -w packages/gitlab (93/93), root npm run typecheck (55/55 tasks) and root npm test (105/105 tasks) post-rebase.
  • One shared-runtime change to flag: packages/core/src/runtime/file-native-router.ts readExactFileResourceId no longer requires equal segment counts, so a {projectPath} placeholder can span a nested GitLab namespace (group/subgroup/project). Fixed suffix segments are still matched from the right; only placeholder templates are affected.
  • packages/core gained mapping + generated catalog entries but its version is not bumped here — the publish workflow's changed-package mode should pick it up; shout if you want an explicit core bump in this PR.
  • Follow-up (out of scope, ../flows): make the f.gitlab helper's read/list gap visible or close it, per the "second-class provider" note that prompted this.

🤖 Generated with Claude Code

Review in cubic


Note

Medium Risk
Expands provider write surface (create branches, MRs, merges) and changes shared exact-file ID matching for nested GitLab project paths; mis-routing could affect non-GitLab adapters using similar templates.

Overview
Adds file-native GitLab writeback for issues, merge requests, branches, and merge-request lifecycle actions—beyond the prior discussions/notes-only surface.

Agents can draft creates under issues/, merge-requests/, and refs/, patch issue/MR meta.json, and drive merge and close/reopen via merge.json / close.json sidecars. GitLabWritebackHandler routes these to GitLab REST v4 with stricter payload validation (allowed fields, read-only rejection, label normalization, state_event checks).

Discovery and contracts are wired through mappings, generated writeback catalog, JSON schemas/examples, layout manifest, and LAYOUT.md. Path helpers and parseGitLabPath understand merge-requests vs merge_requests and new draft/sidecar paths.

In adapter-core, readExactFileResourceId no longer requires equal segment counts so nested {projectPath} namespaces match exact sidecar templates (e.g. group/subgroup/project).

Reviewed by Cursor Bugbot for commit 70de4fa. Bugbot is set up for automated code reviews on this repo. Configure here.

Add GitLab REST v4 creation, branch refs, merge, and lifecycle writeback routes with discovery contracts and catalog entries.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Session-Id: 01a0b7a6-3196-7081-b3db-70fc609b7d28
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 2 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: df305b4a-595f-4613-bbc3-e75a31f020b0

📥 Commits

Reviewing files that changed from the base of the PR and between 5ed1f20 and 70de4fa.

⛔ Files ignored due to path filters (2)
  • packages/core/src/writeback-paths/catalog.generated.json is excluded by !**/*.generated.*
  • packages/core/src/writeback-paths/catalog.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (35)
  • CHANGELOG.md
  • docs/writeback-spec-coverage.md
  • packages/core/mappings/adapters/gitlab.mapping.yaml
  • packages/core/src/runtime/file-native-router.ts
  • packages/gitlab/discovery/gitlab/.adapter.md
  • packages/gitlab/discovery/gitlab/projects/{projectPath}/issues/.create.example.json
  • packages/gitlab/discovery/gitlab/projects/{projectPath}/issues/.schema.json
  • packages/gitlab/discovery/gitlab/projects/{projectPath}/issues/{issueIid}__{slug}/comments/.schema.json
  • packages/gitlab/discovery/gitlab/projects/{projectPath}/issues/{issueIid}__{slug}/meta.json/.create.example.json
  • packages/gitlab/discovery/gitlab/projects/{projectPath}/issues/{issueIid}__{slug}/meta.json/.schema.json
  • packages/gitlab/discovery/gitlab/projects/{projectPath}/merge-requests/.create.example.json
  • packages/gitlab/discovery/gitlab/projects/{projectPath}/merge-requests/.schema.json
  • packages/gitlab/discovery/gitlab/projects/{projectPath}/merge_requests/{mergeRequestIid}__{slug}/close.json/.create.example.json
  • packages/gitlab/discovery/gitlab/projects/{projectPath}/merge_requests/{mergeRequestIid}__{slug}/close.json/.schema.json
  • packages/gitlab/discovery/gitlab/projects/{projectPath}/merge_requests/{mergeRequestIid}__{slug}/discussions/.schema.json
  • packages/gitlab/discovery/gitlab/projects/{projectPath}/merge_requests/{mergeRequestIid}__{slug}/merge.json/.create.example.json
  • packages/gitlab/discovery/gitlab/projects/{projectPath}/merge_requests/{mergeRequestIid}__{slug}/merge.json/.schema.json
  • packages/gitlab/discovery/gitlab/projects/{projectPath}/merge_requests/{mergeRequestIid}__{slug}/meta.json/.create.example.json
  • packages/gitlab/discovery/gitlab/projects/{projectPath}/merge_requests/{mergeRequestIid}__{slug}/meta.json/.schema.json
  • packages/gitlab/discovery/gitlab/projects/{projectPath}/refs/.create.example.json
  • packages/gitlab/discovery/gitlab/projects/{projectPath}/refs/.schema.json
  • packages/gitlab/gitlab.mapping.yaml
  • packages/gitlab/src/index.ts
  • packages/gitlab/src/layout-prompt.ts
  • packages/gitlab/src/layout.ts
  • packages/gitlab/src/path-mapper.ts
  • packages/gitlab/src/resources.ts
  • packages/gitlab/src/writeback.ts
  • packages/gitlab/test/exports.test.ts
  • packages/gitlab/test/layout.test.ts
  • packages/gitlab/test/path-mapper.test.ts
  • packages/gitlab/test/writeback.test.ts
  • scripts/writeback-discovery-data.mjs
  • scripts/writeback-discovery-normalizer.mjs
  • scripts/writeback-discovery-normalizer.test.mjs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment thread packages/gitlab/src/writeback.ts Outdated
};
if (response.status >= 400) return { success: false, error: `${request.method} ${request.endpoint} failed with ${response.status}` };
const result = response.data;
return { success: true, externalId: result?.id ? String(result.id) : result?.iid ? String(result.iid) : undefined };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Created records use global IDs

When GitLab returns both identifiers, writeBack records id instead of project-scoped iid. Create receipts then direct later writes to a nonexistent issue or merge-request number.

Learn more

GitLab issues and merge requests expose a global id and a project-scoped iid. Relayfile mounts these records and addresses REST updates by iid, as shown by canonicalTarget. The generic file-native router uses externalId to rewrite a create draft into its resulting record path. Preferring the global ID therefore creates a valid-looking filename that later targets the wrong /:iid endpoint.

Example: GitLab creates issue { id: 700, iid: 7 }. The receipt currently identifies 700, so an edit targets /issues/700; the created issue is available at /issues/7.

Recommended fix: Prefer result.iid over result.id. Responses without an iid, such as notes and discussions, can continue using id.

Suggested change
return { success: true, externalId: result?.id ? String(result.id) : result?.iid ? String(result.iid) : undefined };
return { success: true, externalId: result?.iid ? String(result.iid) : result?.id ? String(result.id) : undefined };

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 70de4fa9: issue/merge-request receipts prefer iid (fallback id), notes/discussions keep id, branch refs return the branch name. Live re-run against GitLab: issues create receipt is the iid — externalId=7 iid=7 id=203602926, merge-requests … externalId=8 iid=8 id=535560958, refs receipt = branch name. Unit tests added for each case.

Comment on lines +50 to +75
{
name: "merge",
path: "/gitlab/projects/{projectPath}/merge_requests/{mergeRequestIid}__{slug}/merge.json",
pathPattern: /^\/gitlab\/projects\/.+?\/merge_requests\/[1-9]\d*(?:__[^\/]+)?\/merge\.json$/,
idPattern: /^[1-9]\d*(?:__.*)?$/,
schema: "discovery/gitlab/projects/{projectPath}/merge_requests/{mergeRequestIid}__{slug}/merge.json/.schema.json",
createExample: "discovery/gitlab/projects/{projectPath}/merge_requests/{mergeRequestIid}__{slug}/merge.json/.create.example.json",
operations: ["update"],
},
{
name: "close-merge-request",
path: "/gitlab/projects/{projectPath}/merge_requests/{mergeRequestIid}__{slug}/close.json",
pathPattern: /^\/gitlab\/projects\/.+?\/merge_requests\/[1-9]\d*(?:__[^\/]+)?\/close\.json$/,
idPattern: /^[1-9]\d*(?:__.*)?$/,
schema: "discovery/gitlab/projects/{projectPath}/merge_requests/{mergeRequestIid}__{slug}/close.json/.schema.json",
createExample: "discovery/gitlab/projects/{projectPath}/merge_requests/{mergeRequestIid}__{slug}/close.json/.create.example.json",
operations: ["update"],
},
{
name: "refs",
path: "/gitlab/projects/{projectPath}/refs",
pathPattern: /^\/gitlab\/projects\/.+?\/refs(?:\/[^\/]+(?:\.json)?)?$/,
idPattern: /^$/,
schema: "discovery/gitlab/projects/{projectPath}/refs/.schema.json",
createExample: "discovery/gitlab/projects/{projectPath}/refs/.create.example.json",
operations: ["create"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 New writebacks lack path helpers

The new merge.json, close.json, refs, and create paths have no typed path-mapper helpers. Consumers cannot construct these writebacks through the adapter's supported path API.

Learn more

The adapter contract requires a typed path-mapper.ts helper for every canonical path the adapter emits. The new resource declarations publish several paths that the existing path mapper cannot construct directly. Generic metadata helpers cover issue and merge-request records, but not create collections, merge/close sidecars, or branch refs.

Example: A consumer can use computeMetadataPath for a merge-request meta.json, but must concatenate strings to produce its sibling merge.json. That string construction is explicitly unsupported and can drift from the catalog.

Recommended fix: Add typed helpers for the issue and merge-request create directories, branch-ref drafts, and merge-request merge/close sidecars. Add compose-and-parse round-trip tests for each helper.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 70de4fa9: computeIssueCreateDraftPath, computeMergeRequestCreateDraftPath, computeRefCreateDraftPath, computeMergeRequestMergePath, computeMergeRequestClosePath, exported from the package, with compose→parse round-trip tests including a nested group/subgroup/project path.

Comment thread packages/gitlab/package.json Outdated
{
"name": "@relayfile/adapter-gitlab",
"version": "0.4.2",
"version": "0.5.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Package declares an unpublished version

The feature PR changes version to 0.5.0 before the release workflow runs. Consumers can pin a version that the workflow never publishes.

Learn more

Repository releases bump package versions during publishing. A feature PR must retain the currently published version because the workflow computes and commits the release bump later. Starting at 0.5.0 can make the workflow publish a later version while repository metadata temporarily advertises an unavailable one.

Example: A consumer reads this feature branch and pins @relayfile/adapter-gitlab@0.5.0. The release workflow applies its own bump and publishes another version, so installation of 0.5.0 fails.

Recommended fix: Restore 0.4.2 here and in package-lock.json; let the publish workflow select and commit the release version.

Suggested change
"version": "0.5.0",
"version": "0.4.2",

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restored to 0.4.2 in 70de4fa9 (package-lock.json diff vs main is clean). The release will be cut with the publish workflow's minor bump.

@khaliqgant

Copy link
Copy Markdown
Member Author

Not ready to merge yet: packages/gitlab/src/writeback.ts currently carries // @ts-nocheck and lost its explicit types (incl. the exported GitLabWritebackRequest interface that cloud's relayfile-writeback-bridge.ts imports), so the emitted .d.ts is any-typed. Restoring the typing and running a real end-to-end writeback test against a live GitLab connection before marking ready. (Draft toggle pending — GraphQL rate-limited.)

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 52f7d19. Configure here.

Comment thread packages/gitlab/src/writeback.ts Outdated
Comment thread packages/gitlab/src/writeback.ts Outdated
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Session-Id: 01a0b7be-e7b0-7631-857e-3fa62f47bbc9
@khaliqgant

Copy link
Copy Markdown
Member Author

Live end-to-end verification (head 6e7aa475)

Drove this branch's built GitLabWritebackHandler through a ConnectionProvider whose proxy() is the Nango proxy — the same boundary cloud's executeGitLabWriteback uses — against the real gitlab-relay connection for project agentworkforce-group/AgentWorkforce-project (Nango production env), then read everything back through the same proxy. 33/33 passed, twice (once on the pre-typing build, once on 6e7aa475):

  • refs create ×3 → branches exist (POST /repository/branches)
  • issues create → issue chore: bump @relayfile/sdk to ^0.1.6 #6 with normalized labels; meta.json update → title changed; comments/draft@note.json → note present; meta.json state_event: close → closed
  • merge-requests create → MR !6; meta.json update → title changed; discussions/draft@discussion.json → discussion present; waited for detailed_merge_status=mergeable; merge.json → state: merged
  • MR !7: close.json close → closed, reopen → opened, close → closed
  • Validation rejected before any request: missing title, missing source_branch, bad state_event, read-only id, unsupported path
  • Nested group/project path encoding (%2F) survives the Nango proxy

Ingest side: nango dryrun fetch-issues / fetch-merge-requests from cloud's nango-integrations (metadata stubbed to the sandbox project) return the written objects with terminal states preserved: issue closed, MR1 merged, MR2 closed.

Consumer check: cloud's packages/web typechecks clean against this branch's packed @relayfile/adapter-gitlab@0.5.0 (the @ts-nocheck regression from the first commit is fixed in 6e7aa475; dist/writeback.d.ts is fully typed and exports GitLabWritebackRequest).

Test artifacts on the sandbox project are tagged relayfile-e2e-*; branches were deleted, issue/MRs closed (merged MR records remain, as merges do).

…th helpers, no pre-bump

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Session-Id: 01a0b7cd-aef0-75e1-9e1b-17ef1c310831
@khaliqgant
khaliqgant merged commit 09e3c53 into main Sep 19, 2026
4 checks passed
@khaliqgant
khaliqgant deleted the feat/gitlab-writeback-parity branch September 19, 2026 04:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant