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.
- package/CHANGELOG.md +14 -0
- package/README.md +9 -9
- package/dist/index.js +101 -39
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.cjs +102 -40
- package/dist/mcp-server.cjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
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.8.2](https://gitlab.com/vglafirov/opencode-gitlab-plugin/compare/v2.8.1...v2.8.2) (2026-09-18)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### 🐛 Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **tools:** validate generated GitLab arguments ([69fcfd5](https://gitlab.com/vglafirov/opencode-gitlab-plugin/commit/69fcfd56c8086f600277da8176a9e5c09b0e8ac5))
|
|
11
|
+
|
|
12
|
+
## [2.8.1](https://gitlab.com/vglafirov/opencode-gitlab-plugin/compare/v2.8.0...v2.8.1) (2026-09-06)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **merge-requests:** auto-detect merge train strategy in smart merge ([14fb879](https://gitlab.com/vglafirov/opencode-gitlab-plugin/commit/14fb879b167ed7ada03b02ef83b0944fbfc00480))
|
|
18
|
+
|
|
5
19
|
## [2.8.0](https://gitlab.com/vglafirov/opencode-gitlab-plugin/compare/v2.7.0...v2.8.0) (2026-09-06)
|
|
6
20
|
|
|
7
21
|
|
package/README.md
CHANGED
|
@@ -389,15 +389,15 @@ The plugin provides **64 tools** organized into the following categories:
|
|
|
389
389
|
|
|
390
390
|
### Repository Tools (7 tools)
|
|
391
391
|
|
|
392
|
-
| Tool | Description
|
|
393
|
-
| ----------------------------- |
|
|
394
|
-
| `gitlab_get_file` | Get file contents from
|
|
395
|
-
| `gitlab_get_commit` | Get commit details with metadata, author, and stats
|
|
396
|
-
| `gitlab_list_commits` | List commits with filtering by branch, path, and dates
|
|
397
|
-
| `gitlab_get_commit_diff` | Get diff for a specific commit
|
|
398
|
-
| `gitlab_list_repository_tree` | List files and directories at a given path
|
|
399
|
-
| `gitlab_list_branches` | List all branches in a repository
|
|
400
|
-
| `gitlab_get_commit_comments` | Get all commit comments in flat structure
|
|
392
|
+
| Tool | Description |
|
|
393
|
+
| ----------------------------- | ------------------------------------------------------------- |
|
|
394
|
+
| `gitlab_get_file` | Get file contents from an explicit branch, tag, or commit ref |
|
|
395
|
+
| `gitlab_get_commit` | Get commit details with metadata, author, and stats |
|
|
396
|
+
| `gitlab_list_commits` | List commits with filtering by branch, path, and dates |
|
|
397
|
+
| `gitlab_get_commit_diff` | Get diff for a specific commit |
|
|
398
|
+
| `gitlab_list_repository_tree` | List files and directories at a given path |
|
|
399
|
+
| `gitlab_list_branches` | List all branches in a repository |
|
|
400
|
+
| `gitlab_get_commit_comments` | Get all commit comments in flat structure |
|
|
401
401
|
|
|
402
402
|
### Search Tools (2 tools)
|
|
403
403
|
|
package/dist/index.js
CHANGED
|
@@ -129,19 +129,45 @@ var GitLabApiClient = class {
|
|
|
129
129
|
}
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
-
// src/client/
|
|
133
|
-
|
|
132
|
+
// src/client/connection-pagination.ts
|
|
133
|
+
var DEFAULT_PAGE_SIZE = 20;
|
|
134
|
+
var MAX_PAGE_SIZE = 100;
|
|
135
|
+
function normalizePageSize(value) {
|
|
136
|
+
if (value === void 0 || value === 0) return void 0;
|
|
137
|
+
if (!Number.isFinite(value)) return DEFAULT_PAGE_SIZE;
|
|
138
|
+
return Math.min(Math.max(Math.floor(value), 1), MAX_PAGE_SIZE);
|
|
139
|
+
}
|
|
140
|
+
function buildConnectionVariables(options) {
|
|
134
141
|
const variables = {};
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
const first = normalizePageSize(options?.first);
|
|
143
|
+
const last = normalizePageSize(options?.last);
|
|
144
|
+
if (first !== void 0 && last !== void 0) {
|
|
145
|
+
throw new Error("Pagination accepts either 'first' or 'last', not both");
|
|
146
|
+
}
|
|
147
|
+
if (options?.after && last !== void 0) {
|
|
148
|
+
throw new Error("Pagination cursor 'after' cannot be combined with 'last'");
|
|
149
|
+
}
|
|
150
|
+
if (options?.before && first !== void 0) {
|
|
151
|
+
throw new Error("Pagination cursor 'before' cannot be combined with 'first'");
|
|
152
|
+
}
|
|
153
|
+
if (first !== void 0) {
|
|
154
|
+
variables.first = first;
|
|
155
|
+
} else if (last !== void 0) {
|
|
156
|
+
variables.last = last;
|
|
157
|
+
} else if (options?.before) {
|
|
158
|
+
variables.last = DEFAULT_PAGE_SIZE;
|
|
159
|
+
} else {
|
|
160
|
+
variables.first = DEFAULT_PAGE_SIZE;
|
|
139
161
|
}
|
|
140
162
|
if (options?.after) variables.after = options.after;
|
|
141
|
-
if (options?.last !== void 0) variables.last = options.last;
|
|
142
163
|
if (options?.before) variables.before = options.before;
|
|
143
164
|
return variables;
|
|
144
165
|
}
|
|
166
|
+
|
|
167
|
+
// src/client/notes-types.ts
|
|
168
|
+
function buildPaginationVariables(options) {
|
|
169
|
+
return buildConnectionVariables(options);
|
|
170
|
+
}
|
|
145
171
|
var NOTES_FRAGMENT = `
|
|
146
172
|
fragment NoteFields on Note {
|
|
147
173
|
id
|
|
@@ -180,16 +206,7 @@ var NOTES_CONNECTION_FRAGMENT = `
|
|
|
180
206
|
|
|
181
207
|
// src/client/discussions-types.ts
|
|
182
208
|
function buildDiscussionsPaginationVariables(options) {
|
|
183
|
-
|
|
184
|
-
if (options?.first !== void 0) {
|
|
185
|
-
variables.first = options.first;
|
|
186
|
-
} else if (options?.last == null) {
|
|
187
|
-
variables.first = 20;
|
|
188
|
-
}
|
|
189
|
-
if (options?.after) variables.after = options.after;
|
|
190
|
-
if (options?.last !== void 0) variables.last = options.last;
|
|
191
|
-
if (options?.before) variables.before = options.before;
|
|
192
|
-
return variables;
|
|
209
|
+
return buildConnectionVariables(options);
|
|
193
210
|
}
|
|
194
211
|
var DISCUSSION_NOTE_FRAGMENT = `
|
|
195
212
|
fragment DiscussionNoteFields on Note {
|
|
@@ -705,11 +722,48 @@ var MergeRequestsClient = class _MergeRequestsClient extends GitLabApiClient {
|
|
|
705
722
|
Object.keys(body).length > 0 ? body : void 0
|
|
706
723
|
);
|
|
707
724
|
}
|
|
725
|
+
/**
|
|
726
|
+
* Resolve the auto-merge strategy for a project.
|
|
727
|
+
*
|
|
728
|
+
* When the caller does not specify a strategy, projects with merge trains
|
|
729
|
+
* enabled require ADD_TO_MERGE_TRAIN_WHEN_CHECKS_PASS; otherwise
|
|
730
|
+
* MERGE_WHEN_CHECKS_PASS is correct. Using the wrong one causes auto-merge to
|
|
731
|
+
* fail with an opaque "The merge failed" error. Falls back to
|
|
732
|
+
* MERGE_WHEN_CHECKS_PASS if the project setting cannot be read.
|
|
733
|
+
*
|
|
734
|
+
* Returns both the chosen strategy and how it was arrived at, so the caller
|
|
735
|
+
* can thread a breadcrumb into its result. A bare fallback would collapse a
|
|
736
|
+
* 401, a network timeout, and a genuine 404 all into a silent MWPS default,
|
|
737
|
+
* reintroducing the opaque "The merge failed" this behaviour exists to
|
|
738
|
+
* eliminate; `detection` makes that observable without changing the fallback.
|
|
739
|
+
*/
|
|
740
|
+
async resolveAutoMergeStrategy(projectId) {
|
|
741
|
+
try {
|
|
742
|
+
const encodedProject = this.encodeProjectId(projectId);
|
|
743
|
+
const project = await this.fetch(
|
|
744
|
+
"GET",
|
|
745
|
+
`/projects/${encodedProject}`
|
|
746
|
+
);
|
|
747
|
+
return project.merge_trains_enabled ? {
|
|
748
|
+
strategy: "ADD_TO_MERGE_TRAIN_WHEN_CHECKS_PASS",
|
|
749
|
+
detection: "merge_trains_enabled"
|
|
750
|
+
} : { strategy: "MERGE_WHEN_CHECKS_PASS", detection: "merge_trains_disabled" };
|
|
751
|
+
} catch (error) {
|
|
752
|
+
return {
|
|
753
|
+
strategy: "MERGE_WHEN_CHECKS_PASS",
|
|
754
|
+
detection: "defaulted_after_error",
|
|
755
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
}
|
|
708
759
|
/**
|
|
709
760
|
* Smart merge: merge immediately if checks pass, otherwise set auto-merge
|
|
710
|
-
* Returns detailed context about what happened
|
|
761
|
+
* Returns detailed context about what happened.
|
|
762
|
+
*
|
|
763
|
+
* If `strategy` is omitted, it is auto-detected from the project's merge
|
|
764
|
+
* train settings (merge-train projects need ADD_TO_MERGE_TRAIN_WHEN_CHECKS_PASS).
|
|
711
765
|
*/
|
|
712
|
-
async smartMerge(projectId, mrIid, sha, strategy
|
|
766
|
+
async smartMerge(projectId, mrIid, sha, strategy) {
|
|
713
767
|
let mr = await this.getMergeRequest(projectId, mrIid);
|
|
714
768
|
if (mr.detailed_merge_status === "checking") {
|
|
715
769
|
for (let attempt = 0; attempt < 5 && mr.detailed_merge_status === "checking"; attempt++) {
|
|
@@ -757,15 +811,24 @@ var MergeRequestsClient = class _MergeRequestsClient extends GitLabApiClient {
|
|
|
757
811
|
}
|
|
758
812
|
const autoMergeStatuses = ["ci_still_running", "not_approved"];
|
|
759
813
|
if (autoMergeStatuses.includes(mr.detailed_merge_status)) {
|
|
814
|
+
let resolvedStrategy;
|
|
815
|
+
if (strategy) {
|
|
816
|
+
resolvedStrategy = strategy;
|
|
817
|
+
context.strategyDetection = `explicit: ${strategy}`;
|
|
818
|
+
} else {
|
|
819
|
+
const detected = await this.resolveAutoMergeStrategy(projectId);
|
|
820
|
+
resolvedStrategy = detected.strategy;
|
|
821
|
+
context.strategyDetection = detected.detection === "defaulted_after_error" ? `failed, defaulted to MERGE_WHEN_CHECKS_PASS${detected.reason ? ` (${detected.reason})` : ""}` : `auto-detected (${detected.detection}): ${detected.strategy}`;
|
|
822
|
+
}
|
|
760
823
|
try {
|
|
761
|
-
const result = await this.setAutoMerge(projectId, mrIid, sha,
|
|
824
|
+
const result = await this.setAutoMerge(projectId, mrIid, sha, resolvedStrategy);
|
|
762
825
|
const autoMergeMessages = {
|
|
763
826
|
ci_still_running: "pipeline passes",
|
|
764
827
|
not_approved: "approved"
|
|
765
828
|
};
|
|
766
829
|
return {
|
|
767
830
|
action: "auto_merge_enabled",
|
|
768
|
-
message: `Auto-merge enabled (${
|
|
831
|
+
message: `Auto-merge enabled (${resolvedStrategy}). MR will merge when ${autoMergeMessages[mr.detailed_merge_status]}.`,
|
|
769
832
|
mergeRequest: result,
|
|
770
833
|
context
|
|
771
834
|
};
|
|
@@ -1403,10 +1466,7 @@ var RepositoryClient = class extends GitLabApiClient {
|
|
|
1403
1466
|
async getFile(projectId, filePath, ref) {
|
|
1404
1467
|
const encodedProject = this.encodeProjectId(projectId);
|
|
1405
1468
|
const encodedPath = encodeURIComponent(filePath);
|
|
1406
|
-
|
|
1407
|
-
if (ref) {
|
|
1408
|
-
url += `?ref=${encodeURIComponent(ref)}`;
|
|
1409
|
-
}
|
|
1469
|
+
const url = `/projects/${encodedProject}/repository/files/${encodedPath}?ref=${encodeURIComponent(ref)}`;
|
|
1410
1470
|
const file = await this.fetch("GET", url);
|
|
1411
1471
|
if (file.encoding === "base64") {
|
|
1412
1472
|
return Buffer.from(file.content, "base64").toString("utf-8");
|
|
@@ -3009,17 +3069,12 @@ Note: You must provide the current HEAD SHA of the MR to prevent race conditions
|
|
|
3009
3069
|
"The HEAD SHA of the merge request. Get this from the MR details (diff_refs.head_sha or sha field)."
|
|
3010
3070
|
),
|
|
3011
3071
|
strategy: z.enum(["MERGE_WHEN_CHECKS_PASS", "ADD_TO_MERGE_TRAIN_WHEN_CHECKS_PASS"]).optional().describe(
|
|
3012
|
-
"Auto-merge strategy when immediate merge is not possible.
|
|
3072
|
+
"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."
|
|
3013
3073
|
)
|
|
3014
3074
|
},
|
|
3015
3075
|
execute: async (args, _ctx) => {
|
|
3016
3076
|
const client = getGitLabClient();
|
|
3017
|
-
const result = await client.smartMerge(
|
|
3018
|
-
args.project_id,
|
|
3019
|
-
args.mr_iid,
|
|
3020
|
-
args.sha,
|
|
3021
|
-
args.strategy || "MERGE_WHEN_CHECKS_PASS"
|
|
3022
|
-
);
|
|
3077
|
+
const result = await client.smartMerge(args.project_id, args.mr_iid, args.sha, args.strategy);
|
|
3023
3078
|
return JSON.stringify(result, null, 2);
|
|
3024
3079
|
}
|
|
3025
3080
|
}),
|
|
@@ -3505,13 +3560,13 @@ var repositoryTools = {
|
|
|
3505
3560
|
gitlab_get_file: tool5({
|
|
3506
3561
|
description: `Get the contents of a file from a repository.
|
|
3507
3562
|
Supports fetching files from any branch, tag, or commit SHA.
|
|
3508
|
-
|
|
3563
|
+
An explicit ref is required by the GitLab repository files API.
|
|
3509
3564
|
Note: Invalid refs will result in a 404 error from the GitLab API.`,
|
|
3510
3565
|
args: {
|
|
3511
3566
|
project_id: z5.string().describe("The project ID or URL-encoded path"),
|
|
3512
3567
|
file_path: z5.string().describe("Path to the file in the repository"),
|
|
3513
|
-
ref: z5.string().
|
|
3514
|
-
|
|
3568
|
+
ref: z5.string().trim().min(1).describe(
|
|
3569
|
+
"Branch name, tag, or commit SHA to fetch the file from. Supports full or short commit SHAs."
|
|
3515
3570
|
)
|
|
3516
3571
|
},
|
|
3517
3572
|
execute: async (args, _ctx) => {
|
|
@@ -3689,6 +3744,8 @@ Scopes and their requirements:
|
|
|
3689
3744
|
- wiki_blobs: Search wiki content (supports ref filter)
|
|
3690
3745
|
- group_projects: Search projects within a group (requires group_id)
|
|
3691
3746
|
|
|
3747
|
+
For scopes other than milestones, state="all" is treated as omitted because it does not filter results.
|
|
3748
|
+
|
|
3692
3749
|
Examples:
|
|
3693
3750
|
- Issues: scope="issues", search="bug", project_id="my-group/my-project"
|
|
3694
3751
|
- Code: scope="blobs", search="function calculateTotal"
|
|
@@ -3723,6 +3780,9 @@ Examples:
|
|
|
3723
3780
|
state: z6.enum(["active", "closed", "all"]).optional().describe("Filter by state (for milestones scope)")
|
|
3724
3781
|
},
|
|
3725
3782
|
execute: async (args, _ctx) => {
|
|
3783
|
+
if (args.scope !== "milestones" && args.state === "all") {
|
|
3784
|
+
args.state = void 0;
|
|
3785
|
+
}
|
|
3726
3786
|
validateSearchParams(args.scope, {
|
|
3727
3787
|
project_id: args.project_id,
|
|
3728
3788
|
group_id: args.group_id,
|
|
@@ -4431,10 +4491,10 @@ Examples:
|
|
|
4431
4491
|
"Filter by resolved status: true for resolved, false for unresolved. Only returns resolvable discussions (excludes system notes). Client-side filtering."
|
|
4432
4492
|
),
|
|
4433
4493
|
// Pagination
|
|
4434
|
-
first: z13.number().optional().describe("Number of items to return (default: 20)"),
|
|
4494
|
+
first: z13.number().int().min(0).max(100).optional().describe("Number of items to return (default: 20, max: 100; 0 uses the default)"),
|
|
4435
4495
|
after: z13.string().optional().describe("Cursor for pagination - use endCursor from previous response"),
|
|
4436
4496
|
before: z13.string().optional().describe("Cursor for backward pagination"),
|
|
4437
|
-
last: z13.number().optional().describe("Number of items from the end")
|
|
4497
|
+
last: z13.number().int().min(0).max(100).optional().describe("Number of items from the end (max: 100; 0 is omitted)")
|
|
4438
4498
|
},
|
|
4439
4499
|
execute: async (args, _ctx) => {
|
|
4440
4500
|
validateResourceParams(args.resource_type, args);
|
|
@@ -4747,9 +4807,11 @@ Examples:
|
|
|
4747
4807
|
"Filter by resolved status: true for resolved, false for unresolved. Only returns resolvable notes (excludes system notes). Client-side filtering."
|
|
4748
4808
|
),
|
|
4749
4809
|
// Pagination
|
|
4750
|
-
first: z14.number().
|
|
4810
|
+
first: z14.number().int().min(0).max(100).optional().describe(
|
|
4811
|
+
"Number of items to return from the beginning (default: 20, max: 100; 0 uses the default)"
|
|
4812
|
+
),
|
|
4751
4813
|
after: z14.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
|
|
4752
|
-
last: z14.number().optional().describe("Number of items to return from the end (
|
|
4814
|
+
last: z14.number().int().min(0).max(100).optional().describe("Number of items to return from the end (max: 100; 0 is omitted)"),
|
|
4753
4815
|
before: z14.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
|
|
4754
4816
|
},
|
|
4755
4817
|
execute: async (args, _ctx) => {
|