@gitlab/opencode-gitlab-plugin 1.1.0 → 1.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,25 @@
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
+ ## [1.3.0](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/compare/v1.2.0...v1.3.0) (2026-01-30)
6
+
7
+
8
+ ### ✨ Features
9
+
10
+ * **notes:** migrate notes listing to GraphQL API with pagination support ([2ec5d65](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/2ec5d6528b27b86a08f5c747f38762f90eb1f297))
11
+
12
+
13
+ ### 🐛 Bug Fixes
14
+
15
+ * address review feedback for notes GraphQL migration ([fcb1a35](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/fcb1a355f3ecbddf2429b0e2fecaeb1f02d29ffe))
16
+
17
+ ## [1.2.0](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/compare/v1.1.0...v1.2.0) (2026-01-29)
18
+
19
+
20
+ ### ✨ Features
21
+
22
+ * **merge-requests:** add auto-merge (MWPS) tool using GraphQL API ([2bb3a21](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/2bb3a2123058616cd27003f885ad4d4082e4b3a0))
23
+
5
24
  ## [1.1.0](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/compare/v1.0.6...v1.1.0) (2026-01-13)
6
25
 
7
26
 
package/dist/index.js CHANGED
@@ -82,7 +82,110 @@ var GitLabApiClient = class {
82
82
  }
83
83
  };
84
84
 
85
+ // src/client/notes-types.ts
86
+ function buildPaginationVariables(options) {
87
+ const variables = {};
88
+ if (options?.first !== void 0) {
89
+ variables.first = options.first;
90
+ } else if (options?.last == null) {
91
+ variables.first = 20;
92
+ }
93
+ if (options?.after) variables.after = options.after;
94
+ if (options?.last !== void 0) variables.last = options.last;
95
+ if (options?.before) variables.before = options.before;
96
+ if (options?.filter) variables.filter = options.filter;
97
+ return variables;
98
+ }
99
+ var NOTES_FRAGMENT = `
100
+ fragment NoteFields on Note {
101
+ id
102
+ body
103
+ bodyHtml
104
+ createdAt
105
+ updatedAt
106
+ system
107
+ internal
108
+ resolvable
109
+ resolved
110
+ resolvedAt
111
+ url
112
+ author {
113
+ id
114
+ username
115
+ name
116
+ avatarUrl
117
+ }
118
+ }
119
+ `;
120
+ var NOTES_CONNECTION_FRAGMENT = `
121
+ fragment NotesConnectionFields on NoteConnection {
122
+ count
123
+ pageInfo {
124
+ hasNextPage
125
+ hasPreviousPage
126
+ startCursor
127
+ endCursor
128
+ }
129
+ nodes {
130
+ ...NoteFields
131
+ }
132
+ }
133
+ `;
134
+
85
135
  // src/client/merge-requests.ts
136
+ var LIST_MR_NOTES_QUERY = `
137
+ ${NOTES_FRAGMENT}
138
+ ${NOTES_CONNECTION_FRAGMENT}
139
+ query listMrNotes(
140
+ $projectPath: ID!
141
+ $mrIid: String!
142
+ $first: Int
143
+ $after: String
144
+ $last: Int
145
+ $before: String
146
+ $filter: WorkItemNotesFilterType
147
+ ) {
148
+ project(fullPath: $projectPath) {
149
+ mergeRequest(iid: $mrIid) {
150
+ notes(
151
+ first: $first
152
+ after: $after
153
+ last: $last
154
+ before: $before
155
+ filter: $filter
156
+ ) {
157
+ ...NotesConnectionFields
158
+ }
159
+ }
160
+ }
161
+ }
162
+ `;
163
+ var SET_AUTO_MERGE_MUTATION = `
164
+ mutation setAutoMerge(
165
+ $projectPath: ID!
166
+ $iid: String!
167
+ $sha: String!
168
+ $strategy: MergeStrategyEnum
169
+ ) {
170
+ mergeRequestAccept(
171
+ input: {
172
+ projectPath: $projectPath
173
+ iid: $iid
174
+ sha: $sha
175
+ strategy: $strategy
176
+ }
177
+ ) {
178
+ mergeRequest {
179
+ id
180
+ iid
181
+ title
182
+ autoMergeEnabled
183
+ autoMergeStrategy
184
+ }
185
+ errors
186
+ }
187
+ }
188
+ `;
86
189
  var MergeRequestsClient = class extends GitLabApiClient {
87
190
  async getMergeRequest(projectId, mrIid, includeChanges) {
88
191
  const encodedProject = this.encodeProjectId(projectId);
@@ -157,12 +260,25 @@ var MergeRequestsClient = class extends GitLabApiClient {
157
260
  requestBody
158
261
  );
159
262
  }
160
- async listMrNotes(projectId, mrIid) {
161
- const encodedProject = this.encodeProjectId(projectId);
162
- return this.fetch(
163
- "GET",
164
- `/projects/${encodedProject}/merge_requests/${mrIid}/notes`
165
- );
263
+ /**
264
+ * List notes on a merge request using GraphQL API with pagination support
265
+ */
266
+ async listMrNotes(projectId, mrIid, options) {
267
+ const variables = {
268
+ projectPath: projectId,
269
+ mrIid: String(mrIid),
270
+ ...buildPaginationVariables(options)
271
+ };
272
+ const result = await this.fetchGraphQL(LIST_MR_NOTES_QUERY, variables);
273
+ const notes = result.project?.mergeRequest?.notes;
274
+ if (!notes) {
275
+ throw new Error("Merge request not found or access denied");
276
+ }
277
+ return {
278
+ notes,
279
+ pageInfo: notes.pageInfo,
280
+ totalCount: notes.count
281
+ };
166
282
  }
167
283
  async createMrNote(projectId, mrIid, body, discussionId) {
168
284
  const encodedProject = this.encodeProjectId(projectId);
@@ -209,10 +325,6 @@ var MergeRequestsClient = class extends GitLabApiClient {
209
325
  `/projects/${encodedProject}/merge_requests/${mrIid}/pipelines`
210
326
  );
211
327
  }
212
- /**
213
- * List merge request diffs with pagination support
214
- * API: GET /projects/:id/merge_requests/:merge_request_iid/diffs
215
- */
216
328
  async listMergeRequestDiffs(projectId, mrIid, options = {}) {
217
329
  const encodedProject = this.encodeProjectId(projectId);
218
330
  const params = new URLSearchParams();
@@ -222,9 +334,55 @@ var MergeRequestsClient = class extends GitLabApiClient {
222
334
  const path2 = `/projects/${encodedProject}/merge_requests/${mrIid}/diffs${query ? `?${query}` : ""}`;
223
335
  return this.fetch("GET", path2);
224
336
  }
337
+ /**
338
+ * Set auto-merge (MWPS) on a merge request using GraphQL API
339
+ * Uses the mergeRequestAccept mutation with a merge strategy
340
+ */
341
+ async setAutoMerge(projectId, mrIid, sha, strategy = "MERGE_WHEN_CHECKS_PASS") {
342
+ const result = await this.fetchGraphQL(SET_AUTO_MERGE_MUTATION, {
343
+ projectPath: projectId,
344
+ iid: String(mrIid),
345
+ sha,
346
+ strategy
347
+ });
348
+ if (result.mergeRequestAccept.errors.length > 0) {
349
+ throw new Error(`Failed to set auto-merge: ${result.mergeRequestAccept.errors.join(", ")}`);
350
+ }
351
+ if (!result.mergeRequestAccept.mergeRequest) {
352
+ throw new Error("Failed to set auto-merge: No merge request returned");
353
+ }
354
+ return result.mergeRequestAccept.mergeRequest;
355
+ }
225
356
  };
226
357
 
227
358
  // src/client/issues.ts
359
+ var LIST_ISSUE_NOTES_QUERY = `
360
+ ${NOTES_FRAGMENT}
361
+ ${NOTES_CONNECTION_FRAGMENT}
362
+ query listIssueNotes(
363
+ $projectPath: ID!
364
+ $issueIid: String!
365
+ $first: Int
366
+ $after: String
367
+ $last: Int
368
+ $before: String
369
+ $filter: WorkItemNotesFilterType
370
+ ) {
371
+ project(fullPath: $projectPath) {
372
+ issue(iid: $issueIid) {
373
+ notes(
374
+ first: $first
375
+ after: $after
376
+ last: $last
377
+ before: $before
378
+ filter: $filter
379
+ ) {
380
+ ...NotesConnectionFields
381
+ }
382
+ }
383
+ }
384
+ }
385
+ `;
228
386
  var IssuesClient = class extends GitLabApiClient {
229
387
  async createIssue(projectId, title, options) {
230
388
  const encodedProject = this.encodeProjectId(projectId);
@@ -260,12 +418,25 @@ var IssuesClient = class extends GitLabApiClient {
260
418
  }
261
419
  return this.fetch("GET", path2);
262
420
  }
263
- async listIssueNotes(projectId, issueIid) {
264
- const encodedProject = this.encodeProjectId(projectId);
265
- return this.fetch(
266
- "GET",
267
- `/projects/${encodedProject}/issues/${issueIid}/notes`
268
- );
421
+ /**
422
+ * List notes on an issue using GraphQL API with pagination support
423
+ */
424
+ async listIssueNotes(projectId, issueIid, options) {
425
+ const variables = {
426
+ projectPath: projectId,
427
+ issueIid: String(issueIid),
428
+ ...buildPaginationVariables(options)
429
+ };
430
+ const result = await this.fetchGraphQL(LIST_ISSUE_NOTES_QUERY, variables);
431
+ const notes = result.project?.issue?.notes;
432
+ if (!notes) {
433
+ throw new Error("Issue not found or access denied");
434
+ }
435
+ return {
436
+ notes,
437
+ pageInfo: notes.pageInfo,
438
+ totalCount: notes.count
439
+ };
269
440
  }
270
441
  async listIssueDiscussions(projectId, issueIid) {
271
442
  const encodedProject = this.encodeProjectId(projectId);
@@ -312,10 +483,6 @@ var IssuesClient = class extends GitLabApiClient {
312
483
  { resolved: false }
313
484
  );
314
485
  }
315
- /**
316
- * Get a single note from an issue
317
- * API: GET /projects/:id/issues/:issue_iid/notes/:note_id
318
- */
319
486
  async getIssueNote(projectId, issueIid, noteId) {
320
487
  const encodedProject = this.encodeProjectId(projectId);
321
488
  return this.fetch(
@@ -1066,6 +1233,31 @@ var TodosClient = class extends GitLabApiClient {
1066
1233
  };
1067
1234
 
1068
1235
  // src/client/epics.ts
1236
+ var LIST_EPIC_NOTES_QUERY = `
1237
+ ${NOTES_FRAGMENT}
1238
+ ${NOTES_CONNECTION_FRAGMENT}
1239
+ query listEpicNotes(
1240
+ $groupPath: ID!
1241
+ $epicIid: ID!
1242
+ $first: Int
1243
+ $after: String
1244
+ $last: Int
1245
+ $before: String
1246
+ ) {
1247
+ group(fullPath: $groupPath) {
1248
+ epic(iid: $epicIid) {
1249
+ notes(
1250
+ first: $first
1251
+ after: $after
1252
+ last: $last
1253
+ before: $before
1254
+ ) {
1255
+ ...NotesConnectionFields
1256
+ }
1257
+ }
1258
+ }
1259
+ }
1260
+ `;
1069
1261
  var EpicsClient = class extends GitLabApiClient {
1070
1262
  async getEpic(groupId, epicIid) {
1071
1263
  const encodedGroup = encodeURIComponent(groupId);
@@ -1114,12 +1306,25 @@ var EpicsClient = class extends GitLabApiClient {
1114
1306
  `/groups/${encodedGroup}/epics/${epicIid}/issues/${epicIssueId}`
1115
1307
  );
1116
1308
  }
1117
- async listEpicNotes(groupId, epicIid) {
1118
- const encodedGroup = encodeURIComponent(groupId);
1119
- return this.fetch(
1120
- "GET",
1121
- `/groups/${encodedGroup}/epics/${epicIid}/notes`
1122
- );
1309
+ /**
1310
+ * List notes on an epic using GraphQL API with pagination support
1311
+ */
1312
+ async listEpicNotes(groupId, epicIid, options) {
1313
+ const variables = {
1314
+ groupPath: groupId,
1315
+ epicIid: String(epicIid),
1316
+ ...buildPaginationVariables(options)
1317
+ };
1318
+ const result = await this.fetchGraphQL(LIST_EPIC_NOTES_QUERY, variables);
1319
+ const notes = result.group?.epic?.notes;
1320
+ if (!notes) {
1321
+ throw new Error("Epic not found or access denied");
1322
+ }
1323
+ return {
1324
+ notes,
1325
+ pageInfo: notes.pageInfo,
1326
+ totalCount: notes.count
1327
+ };
1123
1328
  }
1124
1329
  async listEpicDiscussions(groupId, epicIid) {
1125
1330
  const encodedGroup = encodeURIComponent(groupId);
@@ -1150,10 +1355,6 @@ var EpicsClient = class extends GitLabApiClient {
1150
1355
  { body }
1151
1356
  );
1152
1357
  }
1153
- /**
1154
- * Get a single note from an epic
1155
- * API: GET /groups/:id/epics/:epic_iid/notes/:note_id
1156
- */
1157
1358
  async getEpicNote(groupId, epicIid, noteId) {
1158
1359
  const encodedGroup = encodeURIComponent(groupId);
1159
1360
  return this.fetch(
@@ -1164,6 +1365,30 @@ var EpicsClient = class extends GitLabApiClient {
1164
1365
  };
1165
1366
 
1166
1367
  // src/client/snippets.ts
1368
+ var LIST_SNIPPET_NOTES_QUERY = `
1369
+ ${NOTES_FRAGMENT}
1370
+ ${NOTES_CONNECTION_FRAGMENT}
1371
+ query listSnippetNotes(
1372
+ $snippetGid: SnippetID!
1373
+ $first: Int
1374
+ $after: String
1375
+ $last: Int
1376
+ $before: String
1377
+ ) {
1378
+ snippets(ids: [$snippetGid]) {
1379
+ nodes {
1380
+ notes(
1381
+ first: $first
1382
+ after: $after
1383
+ last: $last
1384
+ before: $before
1385
+ ) {
1386
+ ...NotesConnectionFields
1387
+ }
1388
+ }
1389
+ }
1390
+ }
1391
+ `;
1167
1392
  var SnippetsClient = class extends GitLabApiClient {
1168
1393
  async listSnippetDiscussions(projectId, snippetId) {
1169
1394
  const encodedProject = this.encodeProjectId(projectId);
@@ -1179,12 +1404,30 @@ var SnippetsClient = class extends GitLabApiClient {
1179
1404
  `/projects/${encodedProject}/snippets/${snippetId}/discussions/${discussionId}`
1180
1405
  );
1181
1406
  }
1182
- async listSnippetNotes(projectId, snippetId) {
1183
- const encodedProject = this.encodeProjectId(projectId);
1184
- return this.fetch(
1185
- "GET",
1186
- `/projects/${encodedProject}/snippets/${snippetId}/notes`
1187
- );
1407
+ /**
1408
+ * List notes on a snippet using GraphQL API with pagination support
1409
+ */
1410
+ async listSnippetNotes(projectId, snippetId, options) {
1411
+ const variables = {
1412
+ snippetGid: `gid://gitlab/ProjectSnippet/${snippetId}`,
1413
+ ...buildPaginationVariables(options)
1414
+ };
1415
+ const result = await this.fetchGraphQL(LIST_SNIPPET_NOTES_QUERY, variables);
1416
+ const notes = result.snippets.nodes[0]?.notes || {
1417
+ nodes: [],
1418
+ pageInfo: {
1419
+ hasNextPage: false,
1420
+ hasPreviousPage: false,
1421
+ startCursor: null,
1422
+ endCursor: null
1423
+ },
1424
+ count: 0
1425
+ };
1426
+ return {
1427
+ notes,
1428
+ pageInfo: notes.pageInfo,
1429
+ totalCount: notes.count
1430
+ };
1188
1431
  }
1189
1432
  async createSnippetNote(projectId, snippetId, body, discussionId) {
1190
1433
  const encodedProject = this.encodeProjectId(projectId);
@@ -1538,17 +1781,32 @@ Note: For a flattened list of all comments, use gitlab_list_mr_notes instead.`,
1538
1781
  }
1539
1782
  }),
1540
1783
  gitlab_list_mr_notes: tool({
1541
- description: `List all notes/comments on a merge request in a flat structure.
1784
+ description: `List all notes/comments on a merge request using GraphQL API with pagination support.
1542
1785
  Returns all comments including system notes, code review comments, and general discussion.
1543
- This is easier to read than discussions which have nested structure.`,
1786
+ This is easier to read than discussions which have nested structure.
1787
+
1788
+ The response includes pagination information (pageInfo) with cursors for fetching additional pages.
1789
+ Use 'after' with the 'endCursor' from pageInfo to get the next page.
1790
+ Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
1544
1791
  args: {
1545
1792
  project_id: z.string().describe("The project ID or URL-encoded path"),
1546
- mr_iid: z.number().describe("The internal ID of the merge request")
1793
+ mr_iid: z.number().describe("The internal ID of the merge request"),
1794
+ first: z.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
1795
+ after: z.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
1796
+ last: z.number().optional().describe("Number of items to return from the end (for backward pagination)"),
1797
+ before: z.string().optional().describe("Cursor for backward pagination - use startCursor from previous response"),
1798
+ filter: z.enum(["ALL_NOTES", "ONLY_COMMENTS", "ONLY_ACTIVITY"]).optional().describe("Filter notes by type: ALL_NOTES (default), ONLY_COMMENTS, or ONLY_ACTIVITY")
1547
1799
  },
1548
1800
  execute: async (args, _ctx) => {
1549
1801
  const client = getGitLabClient();
1550
- const notes = await client.listMrNotes(args.project_id, args.mr_iid);
1551
- return JSON.stringify(notes, null, 2);
1802
+ const result = await client.listMrNotes(args.project_id, args.mr_iid, {
1803
+ first: args.first,
1804
+ after: args.after,
1805
+ last: args.last,
1806
+ before: args.before,
1807
+ filter: args.filter
1808
+ });
1809
+ return JSON.stringify(result, null, 2);
1552
1810
  }
1553
1811
  }),
1554
1812
  gitlab_create_mr_note: tool({
@@ -1775,6 +2033,33 @@ This is useful when you need to process diffs in chunks or when the MR has many
1775
2033
  });
1776
2034
  return JSON.stringify(diffs, null, 2);
1777
2035
  }
2036
+ }),
2037
+ gitlab_set_mr_auto_merge: tool({
2038
+ description: `Enable auto-merge (MWPS - Merge When Pipeline Succeeds) on a merge request.
2039
+ Uses the GitLab GraphQL API mergeRequestAccept mutation with a merge strategy.
2040
+ The MR will be automatically merged when all required conditions are met.
2041
+ Requires the MR to be approved and have a passing pipeline (depending on project settings).
2042
+ Note: You must provide the current HEAD SHA of the MR to prevent race conditions.`,
2043
+ args: {
2044
+ project_id: z.string().describe('The project ID or path (e.g., "gitlab-org/gitlab")'),
2045
+ mr_iid: z.number().describe("The internal ID of the merge request"),
2046
+ sha: z.string().describe(
2047
+ "The HEAD SHA of the merge request. Get this from the MR details (diff_refs.head_sha or sha field)."
2048
+ ),
2049
+ strategy: z.enum(["MERGE_WHEN_CHECKS_PASS", "ADD_TO_MERGE_TRAIN_WHEN_CHECKS_PASS"]).optional().describe(
2050
+ "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."
2051
+ )
2052
+ },
2053
+ execute: async (args, _ctx) => {
2054
+ const client = getGitLabClient();
2055
+ const result = await client.setAutoMerge(
2056
+ args.project_id,
2057
+ args.mr_iid,
2058
+ args.sha,
2059
+ args.strategy || "MERGE_WHEN_CHECKS_PASS"
2060
+ );
2061
+ return JSON.stringify(result, null, 2);
2062
+ }
1778
2063
  })
1779
2064
  };
1780
2065
 
@@ -1854,16 +2139,31 @@ Can filter by state, labels, assignee, milestone.`,
1854
2139
  }
1855
2140
  }),
1856
2141
  gitlab_list_issue_notes: tool2({
1857
- description: `List all notes/comments on an issue.
1858
- Returns all comments including system notes in chronological order.`,
2142
+ description: `List all notes/comments on an issue using GraphQL API with pagination support.
2143
+ Returns all comments including system notes in chronological order.
2144
+
2145
+ The response includes pagination information (pageInfo) with cursors for fetching additional pages.
2146
+ Use 'after' with the 'endCursor' from pageInfo to get the next page.
2147
+ Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
1859
2148
  args: {
1860
2149
  project_id: z2.string().describe("The project ID or URL-encoded path"),
1861
- issue_iid: z2.number().describe("The internal ID of the issue")
2150
+ issue_iid: z2.number().describe("The internal ID of the issue"),
2151
+ first: z2.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
2152
+ after: z2.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
2153
+ last: z2.number().optional().describe("Number of items to return from the end (for backward pagination)"),
2154
+ before: z2.string().optional().describe("Cursor for backward pagination - use startCursor from previous response"),
2155
+ filter: z2.enum(["ALL_NOTES", "ONLY_COMMENTS", "ONLY_ACTIVITY"]).optional().describe("Filter notes by type: ALL_NOTES (default), ONLY_COMMENTS, or ONLY_ACTIVITY")
1862
2156
  },
1863
2157
  execute: async (args, _ctx) => {
1864
2158
  const client = getGitLabClient();
1865
- const notes = await client.listIssueNotes(args.project_id, args.issue_iid);
1866
- return JSON.stringify(notes, null, 2);
2159
+ const result = await client.listIssueNotes(args.project_id, args.issue_iid, {
2160
+ first: args.first,
2161
+ after: args.after,
2162
+ last: args.last,
2163
+ before: args.before,
2164
+ filter: args.filter
2165
+ });
2166
+ return JSON.stringify(result, null, 2);
1867
2167
  }
1868
2168
  }),
1869
2169
  gitlab_list_issue_discussions: tool2({
@@ -2118,16 +2418,29 @@ Removes the association between the issue and the epic.`,
2118
2418
  }
2119
2419
  }),
2120
2420
  gitlab_list_epic_notes: tool3({
2121
- description: `List all comments/notes on an epic in a flat structure.
2122
- Returns all comments in chronological order.`,
2421
+ description: `List all comments/notes on an epic using GraphQL API with pagination support.
2422
+ Returns all comments in chronological order.
2423
+
2424
+ The response includes pagination information (pageInfo) with cursors for fetching additional pages.
2425
+ Use 'after' with the 'endCursor' from pageInfo to get the next page.
2426
+ Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
2123
2427
  args: {
2124
2428
  group_id: z3.string().describe("The group ID or URL-encoded path"),
2125
- epic_iid: z3.number().describe("The internal ID of the epic")
2429
+ epic_iid: z3.number().describe("The internal ID of the epic"),
2430
+ first: z3.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
2431
+ after: z3.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
2432
+ last: z3.number().optional().describe("Number of items to return from the end (for backward pagination)"),
2433
+ before: z3.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
2126
2434
  },
2127
2435
  execute: async (args, _ctx) => {
2128
2436
  const client = getGitLabClient();
2129
- const notes = await client.listEpicNotes(args.group_id, args.epic_iid);
2130
- return JSON.stringify(notes, null, 2);
2437
+ const result = await client.listEpicNotes(args.group_id, args.epic_iid, {
2438
+ first: args.first,
2439
+ after: args.after,
2440
+ last: args.last,
2441
+ before: args.before
2442
+ });
2443
+ return JSON.stringify(result, null, 2);
2131
2444
  }
2132
2445
  }),
2133
2446
  gitlab_list_epic_discussions: tool3({
@@ -3138,16 +3451,29 @@ Use this to get the full context of a specific conversation.`,
3138
3451
  }
3139
3452
  }),
3140
3453
  gitlab_list_snippet_notes: tool9({
3141
- description: `List all notes/comments on a project snippet in a flat structure.
3142
- Returns all comments including system notes in chronological order.`,
3454
+ description: `List all notes/comments on a project snippet using GraphQL API with pagination support.
3455
+ Returns all comments including system notes in chronological order.
3456
+
3457
+ The response includes pagination information (pageInfo) with cursors for fetching additional pages.
3458
+ Use 'after' with the 'endCursor' from pageInfo to get the next page.
3459
+ Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
3143
3460
  args: {
3144
3461
  project_id: z9.string().describe("The project ID or URL-encoded path"),
3145
- snippet_id: z9.number().describe("The ID of the snippet")
3462
+ snippet_id: z9.number().describe("The ID of the snippet"),
3463
+ first: z9.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
3464
+ after: z9.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
3465
+ last: z9.number().optional().describe("Number of items to return from the end (for backward pagination)"),
3466
+ before: z9.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
3146
3467
  },
3147
3468
  execute: async (args, _ctx) => {
3148
3469
  const client = getGitLabClient();
3149
- const notes = await client.listSnippetNotes(args.project_id, args.snippet_id);
3150
- return JSON.stringify(notes, null, 2);
3470
+ const result = await client.listSnippetNotes(args.project_id, args.snippet_id, {
3471
+ first: args.first,
3472
+ after: args.after,
3473
+ last: args.last,
3474
+ before: args.before
3475
+ });
3476
+ return JSON.stringify(result, null, 2);
3151
3477
  }
3152
3478
  }),
3153
3479
  gitlab_create_snippet_note: tool9({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/opencode-gitlab-plugin",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "GitLab tools plugin for OpenCode - provides GitLab API access for merge requests, issues, pipelines, and more",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",