opencode-gitlab-plugin 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
4
4
 
5
+ ## [2.3.0](https://gitlab.com/vglafirov/opencode-gitlab-plugin/compare/v2.2.0...v2.3.0) (2026-05-18)
6
+
7
+
8
+ ### ✨ Features
9
+
10
+ * **merge-requests:** add smart merge with detailed context ([d03da5d](https://gitlab.com/vglafirov/opencode-gitlab-plugin/commit/d03da5d19d3851e1d3a59a07dd3155d307a0369c))
11
+
5
12
  ## [2.2.0](https://gitlab.com/vglafirov/opencode-gitlab-plugin/compare/v2.1.0...v2.2.0) (2026-04-07)
6
13
 
7
14
 
package/dist/index.js CHANGED
@@ -545,6 +545,107 @@ var MergeRequestsClient = class extends GitLabApiClient {
545
545
  `/projects/${encodedProject}/merge_requests/${mrIid}/unapprove`
546
546
  );
547
547
  }
548
+ /**
549
+ * Merge a merge request immediately
550
+ * Requires the MR to be in a mergeable state (approved, pipeline passed, no conflicts)
551
+ */
552
+ async mergeMergeRequest(projectId, mrIid, options) {
553
+ const encodedProject = this.encodeProjectId(projectId);
554
+ const body = {};
555
+ if (options?.sha) body.sha = options.sha;
556
+ if (options?.squash !== void 0) body.squash = options.squash;
557
+ if (options?.shouldRemoveSourceBranch !== void 0) {
558
+ body.should_remove_source_branch = options.shouldRemoveSourceBranch;
559
+ }
560
+ return this.fetch(
561
+ "PUT",
562
+ `/projects/${encodedProject}/merge_requests/${mrIid}/merge`,
563
+ Object.keys(body).length > 0 ? body : void 0
564
+ );
565
+ }
566
+ /**
567
+ * Smart merge: merge immediately if checks pass, otherwise set auto-merge
568
+ * Returns detailed context about what happened
569
+ */
570
+ async smartMerge(projectId, mrIid, sha, strategy = "MERGE_WHEN_CHECKS_PASS") {
571
+ const mr = await this.getMergeRequest(projectId, mrIid);
572
+ const context = {
573
+ state: mr.state,
574
+ detailedMergeStatus: mr.detailed_merge_status,
575
+ pipelineStatus: mr.head_pipeline?.status || null,
576
+ hasConflicts: mr.has_conflicts,
577
+ hasAnyApproval: (mr.approved_by?.length || 0) > 0
578
+ };
579
+ if (mr.state !== "opened") {
580
+ return {
581
+ action: "failed",
582
+ message: `MR is not open (state: ${mr.state})`,
583
+ context
584
+ };
585
+ }
586
+ if (mr.has_conflicts) {
587
+ return {
588
+ action: "failed",
589
+ message: "MR has merge conflicts that must be resolved",
590
+ context
591
+ };
592
+ }
593
+ if (mr.detailed_merge_status === "mergeable") {
594
+ try {
595
+ const result = await this.mergeMergeRequest(projectId, mrIid, { sha: mr.sha });
596
+ return {
597
+ action: "merged",
598
+ message: "MR merged successfully",
599
+ mergeRequest: result,
600
+ context
601
+ };
602
+ } catch (error) {
603
+ return {
604
+ action: "failed",
605
+ message: `Direct merge failed: ${error instanceof Error ? error.message : String(error)}`,
606
+ context
607
+ };
608
+ }
609
+ }
610
+ const autoMergeStatuses = ["ci_still_running", "not_approved", "checking"];
611
+ if (autoMergeStatuses.includes(mr.detailed_merge_status)) {
612
+ try {
613
+ const result = await this.setAutoMerge(projectId, mrIid, sha, strategy);
614
+ const autoMergeMessages = {
615
+ ci_still_running: "pipeline passes",
616
+ not_approved: "approved",
617
+ checking: "checks complete"
618
+ };
619
+ return {
620
+ action: "auto_merge_enabled",
621
+ message: `Auto-merge enabled (${strategy}). MR will merge when ${autoMergeMessages[mr.detailed_merge_status]}.`,
622
+ mergeRequest: result,
623
+ context
624
+ };
625
+ } catch (error) {
626
+ return {
627
+ action: "failed",
628
+ message: `Failed to enable auto-merge: ${error instanceof Error ? error.message : String(error)}. MR status: ${mr.detailed_merge_status}, pipeline: ${mr.head_pipeline?.status || "none"}`,
629
+ context
630
+ };
631
+ }
632
+ }
633
+ const statusHints = {
634
+ discussions_not_resolved: "Resolve all open discussion threads before merging.",
635
+ draft_status: "MR is in draft. Mark as ready before merging.",
636
+ need_rebase: "Source branch needs to be rebased onto the target branch.",
637
+ requested_changes: "A reviewer requested changes that must be addressed.",
638
+ policies_denied: "Merge is blocked by a security/compliance policy.",
639
+ external_status_checks: "External status checks must pass before merging.",
640
+ jira_association_missing: "MR must be linked to a Jira issue.",
641
+ conflict: "MR has merge conflicts that must be resolved."
642
+ };
643
+ return {
644
+ action: "failed",
645
+ message: statusHints[mr.detailed_merge_status] ?? `Cannot merge: ${mr.detailed_merge_status}`,
646
+ context
647
+ };
648
+ }
548
649
  };
549
650
 
550
651
  // src/client/issues.ts
@@ -2709,7 +2810,7 @@ Note: You must provide the current HEAD SHA of the MR to prevent race conditions
2709
2810
  "The HEAD SHA of the merge request. Get this from the MR details (diff_refs.head_sha or sha field)."
2710
2811
  ),
2711
2812
  strategy: z.enum(["MERGE_WHEN_CHECKS_PASS", "ADD_TO_MERGE_TRAIN_WHEN_CHECKS_PASS"]).optional().describe(
2712
- "Auto-merge strategy. MERGE_WHEN_CHECKS_PASS (default) waits for all checks including approvals. ADD_TO_MERGE_TRAIN_WHEN_CHECKS_PASS adds to merge train when checks pass."
2813
+ "MERGE_WHEN_CHECKS_PASS (default) waits for all checks. ADD_TO_MERGE_TRAIN_WHEN_CHECKS_PASS adds to merge train."
2713
2814
  )
2714
2815
  },
2715
2816
  execute: async (args, _ctx) => {
@@ -2723,6 +2824,62 @@ Note: You must provide the current HEAD SHA of the MR to prevent race conditions
2723
2824
  return JSON.stringify(result, null, 2);
2724
2825
  }
2725
2826
  }),
2827
+ gitlab_smart_merge_mr: tool({
2828
+ description: `Smart merge: merges immediately if checks pass, otherwise enables auto-merge (MWPS).
2829
+ This tool automatically determines the best merge action based on MR state:
2830
+ - If MR is mergeable (pipeline passed, approved): merges immediately
2831
+ - If pipeline is running or awaiting approval: enables auto-merge
2832
+ - Returns detailed context about what happened and why
2833
+
2834
+ Provides actionable feedback on failures including MR state, pipeline status, and remediation hints.
2835
+ Note: You must provide the current HEAD SHA of the MR to prevent race conditions.`,
2836
+ args: {
2837
+ project_id: z.string().describe('The project ID or path (e.g., "gitlab-org/gitlab")'),
2838
+ mr_iid: z.number().describe("The internal ID of the merge request"),
2839
+ sha: z.string().describe(
2840
+ "The HEAD SHA of the merge request. Get this from the MR details (diff_refs.head_sha or sha field)."
2841
+ ),
2842
+ strategy: z.enum(["MERGE_WHEN_CHECKS_PASS", "ADD_TO_MERGE_TRAIN_WHEN_CHECKS_PASS"]).optional().describe(
2843
+ "Auto-merge strategy when immediate merge is not possible. MERGE_WHEN_CHECKS_PASS (default) waits for all checks. ADD_TO_MERGE_TRAIN_WHEN_CHECKS_PASS adds to merge train."
2844
+ )
2845
+ },
2846
+ execute: async (args, _ctx) => {
2847
+ const client = getGitLabClient();
2848
+ const result = await client.smartMerge(
2849
+ args.project_id,
2850
+ args.mr_iid,
2851
+ args.sha,
2852
+ args.strategy || "MERGE_WHEN_CHECKS_PASS"
2853
+ );
2854
+ return JSON.stringify(result, null, 2);
2855
+ }
2856
+ }),
2857
+ gitlab_merge_mr: tool({
2858
+ description: `Merge a merge request immediately.
2859
+ Use this when you know the MR is ready to merge (pipeline passed, approved, no conflicts).
2860
+ For automatic handling based on MR state, use gitlab_smart_merge_mr instead.
2861
+
2862
+ WARNING: If sha is omitted, the merge may succeed against a newer commit than what was reviewed/approved.
2863
+ Always provide sha when you want to ensure you're merging the exact code that was reviewed.`,
2864
+ args: {
2865
+ project_id: z.string().describe('The project ID or path (e.g., "gitlab-org/gitlab")'),
2866
+ mr_iid: z.number().describe("The internal ID of the merge request"),
2867
+ sha: z.string().optional().describe(
2868
+ "HEAD SHA to verify before merging. Recommended to prevent merging unreviewed code if new commits were pushed."
2869
+ ),
2870
+ squash: z.boolean().optional().describe("Whether to squash commits (uses project default if not specified)"),
2871
+ should_remove_source_branch: z.boolean().optional().describe("Whether to delete source branch after merge")
2872
+ },
2873
+ execute: async (args, _ctx) => {
2874
+ const client = getGitLabClient();
2875
+ const result = await client.mergeMergeRequest(args.project_id, args.mr_iid, {
2876
+ sha: args.sha,
2877
+ squash: args.squash,
2878
+ shouldRemoveSourceBranch: args.should_remove_source_branch
2879
+ });
2880
+ return JSON.stringify(result, null, 2);
2881
+ }
2882
+ }),
2726
2883
  gitlab_approve_merge_request: tool({
2727
2884
  description: `Approve a merge request.
2728
2885
  Adds the current user's approval to the merge request.