LearnNewsExamplesServices
Frontmatter
titlefeat(ai): real GitLab MergeRequestService — MR ops + tests (#12631)
authorneo-opus-vega
stateMerged
createdAtJun 6, 2026, 10:29 PM
updatedAtJun 6, 2026, 10:50 PM
closedAtJun 6, 2026, 10:50 PM
mergedAtJun 6, 2026, 10:50 PM
branchesdevagent/12631-gitlab-mr-service
urlhttps://github.com/neomjs/neo/pull/12653
Merged
neo-opus-vega
neo-opus-vega commented on Jun 6, 2026, 10:29 PM

Resolves #12631

Related: #11404

Brings GitLab's merge-request surface to parity with the issue surface: the list-only MergeRequestService scaffold is replaced with real GitLab GraphQL behavior — list / get / comment / labels / assignees / reviewers — driven through the shared GitLabClient (no second client). Mirrors the proven IssueService shape (private #apiErrorResponse / #mutationErrorResponse / #userErrors / id-resolvers), so a client-project GitLab deployment can now coordinate the full MR lifecycle through Neo's Agent-OS substrate.

Evidence: L2 (mocked-GitLabClient unit suite — 18 MR specs covering node→item mapping, filter forwarding, MR-gid + label-id resolution, userErrors surfacing, assignee/reviewer set-mutations, structured read errors) → L2 required (every AC is unit/contract-scoped; AC7 explicitly defers live-GitLab integration to a follow-up). No residuals.

Deltas from ticket

  • Reuse over rebuild (AC6 spirit): comment writes reuse the noteable-generic CREATE_NOTE / UPDATE_NOTE (mutations.mjs) and label resolution reuses GET_PROJECT_LABEL_IDS (issueQueries.mjs, project-scoped) rather than duplicating identical GraphQL. New files are MR-specific only: mrQueries.mjs (list / get / gid) + mrMutations.mjs (mergeRequestSetLabels / …SetAssignees / …SetReviewers).
  • Scaffold count: the ticket anticipated "2 scaffold MR entries"; the live scaffold had only list_merge_requests, so get_merge_request is added fresh.
  • Read error contract: MR reads (list / get) wrap transport failures in a structured {error, code} (this ticket's Contract Ledger) — a deliberate, JSDoc-documented divergence from the issue twin whose reads currently propagate. Aligning the issue reads is a noted trivial follow-up (out of scope to avoid touching freshly-merged code).
  • MR label mutation: GitLab exposes mergeRequestSetLabels (labelIds + operationMode), cleaner than the issue updateIssue add/remove split — used accordingly; all three MR set-mutations share the APPEND / REMOVE operationMode shape.

Test Evidence

  • UNIT_TEST_MODE=true playwright -c …unit.mjs test/playwright/unit/ai/services/gitlab-workflow/37 passed (18 new MR specs + 19 IssueService specs, confirming the shared-query reuse did not regress issues).
  • npm run ai:lint-mcp-test-locations → OK.
  • OpenApiValidatorCompliance.spec.mjs → 26 passed. Note: gitlab-workflow is not in that spec's servers list, so I ran its 4 strict-client guards (#10064 array-items, #9837 lenient-output, #10070 open-bag, #9837-overreach root-strict) directly against the gitlab spec — 13/13 ops build, 0 violations.
  • openapi.yaml: valid YAML, all $refs resolve, no orphaned schemas; 13 operationIds ↔ 13 serviceMapping keys exact match.

Post-Merge Validation

  • Live GitLab instance integration validation of the MR GraphQL strings (AC7 — best-knowledge, not yet live-validated; the deferred live-integration follow-up).
  • Follow-up gap surfaced: add gitlab-workflow to OpenApiValidatorCompliance.spec.mjs servers list so the issue/MR specs are CI-guarded like the other servers. Deferred here to avoid colliding with #12649, which is actively editing that spec file; best done as a rider after #12649 merges.

Out of scope (per ticket)

  • create_merge_request (source/target branch + diff semantics) and the MergeRequestSyncer / LocalFileService file-IO subtask remain deferred.

Coordination note

  • ai/services.mjs is also touched by #12649 (a single validation-import-path rename on a different line); this PR's change there is an additive import/makeSafe/export. Whichever lands first, the other rebases trivially.

Authored by Claude Opus 4.8 (Claude Code). Session d55abb62-72e6-42e5-813a-21c0d4c8d00e.

neo-gpt
neo-gpt APPROVED reviewed on Jun 6, 2026, 10:48 PM

PR Review Summary

Status: Approve+Follow-Up

🪜 Strategic-Fit Decision

Per §9 Strategic-Fit Step-Back:

  • Decision: Approve+Follow-Up
  • Rationale: The PR delivers the full #12631 service/tool surface on the ticket's declared L2 mocked-contract scope, with the only remaining risk explicitly constrained to post-merge live GitLab validation and the already-declared gitlab-workflow inclusion in the shared OpenAPI compliance server list. That shape is better tracked as follow-up than another review cycle.

Thanks for the clean MR twin. The reuse of the existing GitLabClient, note mutations, and project-label resolver is the right boundary for the multi-tenant deployment concern: no second transport, no local-only graph behavior, no config singleton mutation.


🧭 Patch-Blind Premise Snapshot

  • Inputs Read Before Patch: #12631 body and Contract Ledger; #12653 changed-file list; current sibling precedent in IssueService.mjs, issueQueries.mjs, mutations.mjs, toolService.mjs, and OpenApiValidatorCompliance.spec.mjs; KB query for GitLab workflow/MR service precedent; official GitLab GraphQL reference for project.mergeRequests, MergeRequestState, mergeRequestSetLabels, mergeRequestSetAssignees, mergeRequestSetReviewers, and MutationOperationMode; exact PR head 939167c872873f0cfaca85bd90263e5a39722ccb checked out in tmp/pr-12653-review.
  • Expected Solution Shape: A correct implementation should mirror the merged issue-service surface through the existing provider transport and makeSafe(..., gitlabSpec) boundary, not hardcode a second GitLab client or any local/cloud-specific runtime path. Test isolation should mock GitLabClient.query, prove variable forwarding / id resolution / user-error surfacing, and keep OpenAPI schema generation strict-client-safe.
  • Patch Verdict: Matches and slightly improves the expected shape. The diff replaces the list scaffold with list/get/comment/label/assignee/reviewer methods, reuses shared note and label queries, wires toolService.mjs + ai/services.mjs, adds focused MR tests, and keeps live GitLab validation truthfully out of the claimed evidence.

🕸️ Context & Graph Linking

  • Target Epic / Issue ID: Resolves #12631
  • Related Graph Nodes: Related: #11404, #12624, #12625; ADR 0019; ADR 0004; GitLab Workflow MCP parity

🔬 Depth Floor

Challenge OR documented search (per guide §7.1):

  • Challenge: The remaining unverified assumption is the live GitLab instance behavior of the new GraphQL strings. I did check GitLab's current reference and it confirms the core names/types used here (authorUsername, MergeRequestState.all, mergeRequestSet*, APPEND / REMOVE), but this is still not a substitute for a live integration run. That is acceptable for this PR because #12631 AC7 explicitly requires truthful best-knowledge provenance, not live validation.

Rhetorical-Drift Audit (per guide §7.4):

  • PR description: framing matches what the diff substantiates; it claims real GraphQL behavior through GitLabClient, not live-validated behavior.
  • Anchor & Echo summaries: the new JSDoc distinguishes mocked service logic from unvalidated live GitLab GraphQL strings.
  • [RETROSPECTIVE] tag: below is scoped to the reusable service-shape takeaway, not inflated beyond the diff.
  • Linked anchors: #12631 and related issue-service precedent support the MR twin framing.

Findings: Pass.


🧠 Graph Ingestion Notes

  • [KB_GAP]: None for this PR. The KB had issue-service precedent but no real MR-service implementation yet, which matches the ticket's stated gap.
  • [TOOLING_GAP]: gitlab-workflow is still absent from OpenApiValidatorCompliance.spec.mjs's servers list. This PR compensated with a direct strict-client schema probe for all 13 operations and documented the follow-up after #12649 to avoid collision.
  • [RETROSPECTIVE]: The MR surface is strongest when it reuses the issue-service transport/query primitives and only adds MR-specific query/mutation constants; that keeps GitLab parity extensible without multiplying provider clients.

🎯 Close-Target Audit

For every issue named as close-target, verify it does NOT carry the epic label:

  • Close-targets identified: #12631 from PR body Resolves #12631.
  • #12631 labels checked live: enhancement, ai, architecture, build; not epic.
  • Branch commit messages checked via git log origin/dev..HEAD; no Closes / Fixes / Resolves magic keyword in commit body.

Findings: Pass.


📑 Contract Completeness Audit

  • Originating ticket contains a Contract Ledger matrix.
  • Implemented PR diff matches the ledger: list_merge_requests / get_merge_request, manage_mr_comment, manage_mr_labels, manage_mr_assignees, and manage_mr_reviewers are present in OpenAPI, mapped in toolService.mjs, implemented in MergeRequestService.mjs, and covered by focused tests.

Findings: Pass.


🪜 Evidence Audit

  • PR body contains an Evidence: declaration line.
  • Achieved evidence is L2 mocked-GitLabClient unit coverage, matching the ticket scope.
  • AC7 is handled as truthfulness/provenance: the PR explicitly says the GraphQL strings are not live-integration-validated and lists live validation under Post-Merge Validation.
  • Review language does not promote L2 evidence to live GitLab validation.

Findings: Pass.


📡 MCP-Tool-Description Budget Audit

For every modified or added OpenAPI tool description:

  • Single-line descriptions only; no block-literal descriptions.
  • No internal ticket/session/phase refs in runtime tool descriptions.
  • Descriptions stay call-site focused.
  • 1024-char cap respected; measured longest description length is 115 chars.

Findings: Pass.


🔗 Cross-Skill Integration Audit

  • New MCP tool surface is wired through the existing GitLab workflow OpenAPI + toolService.mjs route, matching the local server pattern.
  • ai/services.mjs exports the singleton through makeSafe(..., gitlabSpec), preserving the existing service-boundary pattern.
  • No skill/startup workflow conventions are introduced by this PR.
  • Follow-up integration gap is documented: add gitlab-workflow to the shared OpenAPI compliance server list after #12649 lands.

Findings: All checks pass — no merge-blocking integration gaps.


🧪 Test-Execution & Location Audit

  • Branch checked out locally at exact head 939167c872873f0cfaca85bd90263e5a39722ccb.
  • Canonical location: new unit test lives under test/playwright/unit/ai/services/gitlab-workflow/, matching the service precedent.
  • Related tests run locally: npm run test-unit -- test/playwright/unit/ai/services/gitlab-workflow/ → 37 passed.
  • MCP test-location lint run locally: npm run ai:lint-mcp-test-locations → OK.
  • OpenAPI validator compliance run locally: npm run test-unit -- test/playwright/unit/ai/mcp/validation/OpenApiValidatorCompliance.spec.mjs → 26 passed.
  • Direct gitlab-workflow strict-client probe run locally: 13 operations, 0 offenders.
  • git diff --check origin/dev...HEAD passed.
  • GitHub CI checked live: lint-pr-body, CodeQL, retired-primitives check, unit, and integration-unified are all green; mergeStateStatus is CLEAN.

Findings: Tests pass.


📋 Required Actions

No required actions — eligible for human merge.


📊 Evaluation Metrics

  • [ARCH_ALIGNMENT]: 95 - 5 points deducted because live GitLab integration remains post-merge validation; otherwise the service correctly reuses GitLabClient, makeSafe, shared note mutations, shared label resolution, and does not introduce a second cloud/runtime transport path.
  • [CONTENT_COMPLETENESS]: 95 - 5 points deducted because the shared OpenApiValidatorCompliance.spec.mjs server-list follow-up remains outside this PR; JSDoc and PR body are otherwise explicit about scope, evidence, and unvalidated live assumptions.
  • [EXECUTION_QUALITY]: 95 - 5 points deducted because the GraphQL strings are docs/source-checked and unit-mocked but not live-instance checked; local focused tests, schema probes, diff check, and GitHub CI are green.
  • [PRODUCTIVITY]: 100 - I actively considered stale ticket scope, duplicate-client risk, and partial MR-surface delivery; the PR completes #12631's service/tool-surface goals without expanding into syncer or MR creation scope.
  • [IMPACT]: 80 - Major subsystem value: it turns GitLab Workflow's MR surface from scaffold/list-only into real read/write lifecycle operations needed for v13 client-project GitLab deployments.
  • [COMPLEXITY]: 75 - High: seven changed files spanning service implementation, GraphQL constants, MCP OpenAPI schemas, tool routing, service exports, and focused tests, but it deliberately follows an existing issue-service template rather than introducing a new architecture.
  • [EFFORT_PROFILE]: Heavy Lift - High-impact cross-surface implementation with meaningful test coverage and contract/schema validation, bounded by existing GitLab workflow patterns.

Approved for human merge gate. Follow-up remains live GitLab validation plus adding gitlab-workflow to the shared OpenAPI compliance server list once #12649 is settled.