opencode-gitlab-plugin 2.8.0 → 2.8.2

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.
@@ -28893,19 +28893,45 @@ var GitLabApiClient = class {
28893
28893
  }
28894
28894
  };
28895
28895
 
28896
- // src/client/notes-types.ts
28897
- function buildPaginationVariables(options) {
28896
+ // src/client/connection-pagination.ts
28897
+ var DEFAULT_PAGE_SIZE = 20;
28898
+ var MAX_PAGE_SIZE = 100;
28899
+ function normalizePageSize(value) {
28900
+ if (value === void 0 || value === 0) return void 0;
28901
+ if (!Number.isFinite(value)) return DEFAULT_PAGE_SIZE;
28902
+ return Math.min(Math.max(Math.floor(value), 1), MAX_PAGE_SIZE);
28903
+ }
28904
+ function buildConnectionVariables(options) {
28898
28905
  const variables = {};
28899
- if (options?.first !== void 0) {
28900
- variables.first = options.first;
28901
- } else if (options?.last == null) {
28902
- variables.first = 20;
28906
+ const first = normalizePageSize(options?.first);
28907
+ const last = normalizePageSize(options?.last);
28908
+ if (first !== void 0 && last !== void 0) {
28909
+ throw new Error("Pagination accepts either 'first' or 'last', not both");
28910
+ }
28911
+ if (options?.after && last !== void 0) {
28912
+ throw new Error("Pagination cursor 'after' cannot be combined with 'last'");
28913
+ }
28914
+ if (options?.before && first !== void 0) {
28915
+ throw new Error("Pagination cursor 'before' cannot be combined with 'first'");
28916
+ }
28917
+ if (first !== void 0) {
28918
+ variables.first = first;
28919
+ } else if (last !== void 0) {
28920
+ variables.last = last;
28921
+ } else if (options?.before) {
28922
+ variables.last = DEFAULT_PAGE_SIZE;
28923
+ } else {
28924
+ variables.first = DEFAULT_PAGE_SIZE;
28903
28925
  }
28904
28926
  if (options?.after) variables.after = options.after;
28905
- if (options?.last !== void 0) variables.last = options.last;
28906
28927
  if (options?.before) variables.before = options.before;
28907
28928
  return variables;
28908
28929
  }
28930
+
28931
+ // src/client/notes-types.ts
28932
+ function buildPaginationVariables(options) {
28933
+ return buildConnectionVariables(options);
28934
+ }
28909
28935
  var NOTES_FRAGMENT = `
28910
28936
  fragment NoteFields on Note {
28911
28937
  id
@@ -28944,16 +28970,7 @@ var NOTES_CONNECTION_FRAGMENT = `
28944
28970
 
28945
28971
  // src/client/discussions-types.ts
28946
28972
  function buildDiscussionsPaginationVariables(options) {
28947
- const variables = {};
28948
- if (options?.first !== void 0) {
28949
- variables.first = options.first;
28950
- } else if (options?.last == null) {
28951
- variables.first = 20;
28952
- }
28953
- if (options?.after) variables.after = options.after;
28954
- if (options?.last !== void 0) variables.last = options.last;
28955
- if (options?.before) variables.before = options.before;
28956
- return variables;
28973
+ return buildConnectionVariables(options);
28957
28974
  }
28958
28975
  var DISCUSSION_NOTE_FRAGMENT = `
28959
28976
  fragment DiscussionNoteFields on Note {
@@ -29469,11 +29486,48 @@ var MergeRequestsClient = class _MergeRequestsClient extends GitLabApiClient {
29469
29486
  Object.keys(body).length > 0 ? body : void 0
29470
29487
  );
29471
29488
  }
29489
+ /**
29490
+ * Resolve the auto-merge strategy for a project.
29491
+ *
29492
+ * When the caller does not specify a strategy, projects with merge trains
29493
+ * enabled require ADD_TO_MERGE_TRAIN_WHEN_CHECKS_PASS; otherwise
29494
+ * MERGE_WHEN_CHECKS_PASS is correct. Using the wrong one causes auto-merge to
29495
+ * fail with an opaque "The merge failed" error. Falls back to
29496
+ * MERGE_WHEN_CHECKS_PASS if the project setting cannot be read.
29497
+ *
29498
+ * Returns both the chosen strategy and how it was arrived at, so the caller
29499
+ * can thread a breadcrumb into its result. A bare fallback would collapse a
29500
+ * 401, a network timeout, and a genuine 404 all into a silent MWPS default,
29501
+ * reintroducing the opaque "The merge failed" this behaviour exists to
29502
+ * eliminate; `detection` makes that observable without changing the fallback.
29503
+ */
29504
+ async resolveAutoMergeStrategy(projectId) {
29505
+ try {
29506
+ const encodedProject = this.encodeProjectId(projectId);
29507
+ const project = await this.fetch(
29508
+ "GET",
29509
+ `/projects/${encodedProject}`
29510
+ );
29511
+ return project.merge_trains_enabled ? {
29512
+ strategy: "ADD_TO_MERGE_TRAIN_WHEN_CHECKS_PASS",
29513
+ detection: "merge_trains_enabled"
29514
+ } : { strategy: "MERGE_WHEN_CHECKS_PASS", detection: "merge_trains_disabled" };
29515
+ } catch (error45) {
29516
+ return {
29517
+ strategy: "MERGE_WHEN_CHECKS_PASS",
29518
+ detection: "defaulted_after_error",
29519
+ reason: error45 instanceof Error ? error45.message : String(error45)
29520
+ };
29521
+ }
29522
+ }
29472
29523
  /**
29473
29524
  * Smart merge: merge immediately if checks pass, otherwise set auto-merge
29474
- * Returns detailed context about what happened
29525
+ * Returns detailed context about what happened.
29526
+ *
29527
+ * If `strategy` is omitted, it is auto-detected from the project's merge
29528
+ * train settings (merge-train projects need ADD_TO_MERGE_TRAIN_WHEN_CHECKS_PASS).
29475
29529
  */
29476
- async smartMerge(projectId, mrIid, sha, strategy = "MERGE_WHEN_CHECKS_PASS") {
29530
+ async smartMerge(projectId, mrIid, sha, strategy) {
29477
29531
  let mr = await this.getMergeRequest(projectId, mrIid);
29478
29532
  if (mr.detailed_merge_status === "checking") {
29479
29533
  for (let attempt = 0; attempt < 5 && mr.detailed_merge_status === "checking"; attempt++) {
@@ -29521,15 +29575,24 @@ var MergeRequestsClient = class _MergeRequestsClient extends GitLabApiClient {
29521
29575
  }
29522
29576
  const autoMergeStatuses = ["ci_still_running", "not_approved"];
29523
29577
  if (autoMergeStatuses.includes(mr.detailed_merge_status)) {
29578
+ let resolvedStrategy;
29579
+ if (strategy) {
29580
+ resolvedStrategy = strategy;
29581
+ context.strategyDetection = `explicit: ${strategy}`;
29582
+ } else {
29583
+ const detected = await this.resolveAutoMergeStrategy(projectId);
29584
+ resolvedStrategy = detected.strategy;
29585
+ context.strategyDetection = detected.detection === "defaulted_after_error" ? `failed, defaulted to MERGE_WHEN_CHECKS_PASS${detected.reason ? ` (${detected.reason})` : ""}` : `auto-detected (${detected.detection}): ${detected.strategy}`;
29586
+ }
29524
29587
  try {
29525
- const result = await this.setAutoMerge(projectId, mrIid, sha, strategy);
29588
+ const result = await this.setAutoMerge(projectId, mrIid, sha, resolvedStrategy);
29526
29589
  const autoMergeMessages = {
29527
29590
  ci_still_running: "pipeline passes",
29528
29591
  not_approved: "approved"
29529
29592
  };
29530
29593
  return {
29531
29594
  action: "auto_merge_enabled",
29532
- message: `Auto-merge enabled (${strategy}). MR will merge when ${autoMergeMessages[mr.detailed_merge_status]}.`,
29595
+ message: `Auto-merge enabled (${resolvedStrategy}). MR will merge when ${autoMergeMessages[mr.detailed_merge_status]}.`,
29533
29596
  mergeRequest: result,
29534
29597
  context
29535
29598
  };
@@ -30167,10 +30230,7 @@ var RepositoryClient = class extends GitLabApiClient {
30167
30230
  async getFile(projectId, filePath, ref) {
30168
30231
  const encodedProject = this.encodeProjectId(projectId);
30169
30232
  const encodedPath = encodeURIComponent(filePath);
30170
- let url2 = `/projects/${encodedProject}/repository/files/${encodedPath}`;
30171
- if (ref) {
30172
- url2 += `?ref=${encodeURIComponent(ref)}`;
30173
- }
30233
+ const url2 = `/projects/${encodedProject}/repository/files/${encodedPath}?ref=${encodeURIComponent(ref)}`;
30174
30234
  const file2 = await this.fetch("GET", url2);
30175
30235
  if (file2.encoding === "base64") {
30176
30236
  return Buffer.from(file2.content, "base64").toString("utf-8");
@@ -31779,17 +31839,12 @@ Note: You must provide the current HEAD SHA of the MR to prevent race conditions
31779
31839
  "The HEAD SHA of the merge request. Get this from the MR details (diff_refs.head_sha or sha field)."
31780
31840
  ),
31781
31841
  strategy: z.enum(["MERGE_WHEN_CHECKS_PASS", "ADD_TO_MERGE_TRAIN_WHEN_CHECKS_PASS"]).optional().describe(
31782
- "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."
31842
+ "Auto-merge strategy when immediate merge is not possible. If omitted, it is auto-detected from the project: merge-train projects use ADD_TO_MERGE_TRAIN_WHEN_CHECKS_PASS, otherwise MERGE_WHEN_CHECKS_PASS."
31783
31843
  )
31784
31844
  },
31785
31845
  execute: async (args, _ctx) => {
31786
31846
  const client = getGitLabClient();
31787
- const result = await client.smartMerge(
31788
- args.project_id,
31789
- args.mr_iid,
31790
- args.sha,
31791
- args.strategy || "MERGE_WHEN_CHECKS_PASS"
31792
- );
31847
+ const result = await client.smartMerge(args.project_id, args.mr_iid, args.sha, args.strategy);
31793
31848
  return JSON.stringify(result, null, 2);
31794
31849
  }
31795
31850
  }),
@@ -32271,13 +32326,13 @@ var repositoryTools = {
32271
32326
  gitlab_get_file: tool({
32272
32327
  description: `Get the contents of a file from a repository.
32273
32328
  Supports fetching files from any branch, tag, or commit SHA.
32274
- If ref is not specified, uses the project's default branch.
32329
+ An explicit ref is required by the GitLab repository files API.
32275
32330
  Note: Invalid refs will result in a 404 error from the GitLab API.`,
32276
32331
  args: {
32277
32332
  project_id: z5.string().describe("The project ID or URL-encoded path"),
32278
32333
  file_path: z5.string().describe("Path to the file in the repository"),
32279
- ref: z5.string().optional().describe(
32280
- `Branch name, tag, or commit SHA to fetch the file from. Supports full or short commit SHAs. If omitted, uses the project's default branch (e.g., "main" or "master").`
32334
+ ref: z5.string().trim().min(1).describe(
32335
+ "Branch name, tag, or commit SHA to fetch the file from. Supports full or short commit SHAs."
32281
32336
  )
32282
32337
  },
32283
32338
  execute: async (args, _ctx) => {
@@ -32454,6 +32509,8 @@ Scopes and their requirements:
32454
32509
  - wiki_blobs: Search wiki content (supports ref filter)
32455
32510
  - group_projects: Search projects within a group (requires group_id)
32456
32511
 
32512
+ For scopes other than milestones, state="all" is treated as omitted because it does not filter results.
32513
+
32457
32514
  Examples:
32458
32515
  - Issues: scope="issues", search="bug", project_id="my-group/my-project"
32459
32516
  - Code: scope="blobs", search="function calculateTotal"
@@ -32488,6 +32545,9 @@ Examples:
32488
32545
  state: z6.enum(["active", "closed", "all"]).optional().describe("Filter by state (for milestones scope)")
32489
32546
  },
32490
32547
  execute: async (args, _ctx) => {
32548
+ if (args.scope !== "milestones" && args.state === "all") {
32549
+ args.state = void 0;
32550
+ }
32491
32551
  validateSearchParams(args.scope, {
32492
32552
  project_id: args.project_id,
32493
32553
  group_id: args.group_id,
@@ -33189,10 +33249,10 @@ Examples:
33189
33249
  "Filter by resolved status: true for resolved, false for unresolved. Only returns resolvable discussions (excludes system notes). Client-side filtering."
33190
33250
  ),
33191
33251
  // Pagination
33192
- first: z13.number().optional().describe("Number of items to return (default: 20)"),
33252
+ first: z13.number().int().min(0).max(100).optional().describe("Number of items to return (default: 20, max: 100; 0 uses the default)"),
33193
33253
  after: z13.string().optional().describe("Cursor for pagination - use endCursor from previous response"),
33194
33254
  before: z13.string().optional().describe("Cursor for backward pagination"),
33195
- last: z13.number().optional().describe("Number of items from the end")
33255
+ last: z13.number().int().min(0).max(100).optional().describe("Number of items from the end (max: 100; 0 is omitted)")
33196
33256
  },
33197
33257
  execute: async (args, _ctx) => {
33198
33258
  validateResourceParams(args.resource_type, args);
@@ -33504,9 +33564,11 @@ Examples:
33504
33564
  "Filter by resolved status: true for resolved, false for unresolved. Only returns resolvable notes (excludes system notes). Client-side filtering."
33505
33565
  ),
33506
33566
  // Pagination
33507
- first: z14.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
33567
+ first: z14.number().int().min(0).max(100).optional().describe(
33568
+ "Number of items to return from the beginning (default: 20, max: 100; 0 uses the default)"
33569
+ ),
33508
33570
  after: z14.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
33509
- last: z14.number().optional().describe("Number of items to return from the end (for backward pagination)"),
33571
+ last: z14.number().int().min(0).max(100).optional().describe("Number of items to return from the end (max: 100; 0 is omitted)"),
33510
33572
  before: z14.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
33511
33573
  },
33512
33574
  execute: async (args, _ctx) => {
@@ -34181,7 +34243,7 @@ async function main() {
34181
34243
  ...auditTools,
34182
34244
  ...awardEmojiTools
34183
34245
  };
34184
- const version2 = true ? "2.8.0" : "0.0.0";
34246
+ const version2 = true ? "2.8.2" : "0.0.0";
34185
34247
  const server = new McpServer({ name: "gitlab", version: version2 });
34186
34248
  adaptToolsToMcp(server, allTools);
34187
34249
  const transport = new StdioServerTransport();