opencode-gitlab-plugin 2.6.0 → 2.7.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 +20 -0
- package/README.md +11 -10
- package/dist/index.js +162 -60
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.cjs +163 -61
- package/dist/mcp-server.cjs.map +1 -1
- package/package.json +1 -1
package/dist/mcp-server.cjs
CHANGED
|
@@ -29088,6 +29088,29 @@ var SET_AUTO_MERGE_MUTATION = `
|
|
|
29088
29088
|
}
|
|
29089
29089
|
}
|
|
29090
29090
|
`;
|
|
29091
|
+
var OVERRIDE_REQUESTED_CHANGES_MUTATION = `
|
|
29092
|
+
mutation overrideRequestedChanges(
|
|
29093
|
+
$projectPath: ID!
|
|
29094
|
+
$iid: String!
|
|
29095
|
+
$override: Boolean
|
|
29096
|
+
) {
|
|
29097
|
+
mergeRequestUpdate(
|
|
29098
|
+
input: {
|
|
29099
|
+
projectPath: $projectPath
|
|
29100
|
+
iid: $iid
|
|
29101
|
+
overrideRequestedChanges: $override
|
|
29102
|
+
}
|
|
29103
|
+
) {
|
|
29104
|
+
mergeRequest {
|
|
29105
|
+
id
|
|
29106
|
+
iid
|
|
29107
|
+
title
|
|
29108
|
+
detailedMergeStatus
|
|
29109
|
+
}
|
|
29110
|
+
errors
|
|
29111
|
+
}
|
|
29112
|
+
}
|
|
29113
|
+
`;
|
|
29091
29114
|
var RESOLVE_DISCUSSION_MUTATION = `
|
|
29092
29115
|
mutation resolveDiscussion($discussionId: DiscussionID!, $resolve: Boolean!) {
|
|
29093
29116
|
discussionToggleResolve(input: { id: $discussionId, resolve: $resolve }) {
|
|
@@ -29105,7 +29128,7 @@ var RESOLVE_DISCUSSION_MUTATION = `
|
|
|
29105
29128
|
}
|
|
29106
29129
|
}
|
|
29107
29130
|
`;
|
|
29108
|
-
var MergeRequestsClient = class extends GitLabApiClient {
|
|
29131
|
+
var MergeRequestsClient = class _MergeRequestsClient extends GitLabApiClient {
|
|
29109
29132
|
async getMergeRequest(projectId, mrIid, includeChanges) {
|
|
29110
29133
|
const encodedProject = this.encodeProjectId(projectId);
|
|
29111
29134
|
let path2 = `/projects/${encodedProject}/merge_requests/${mrIid}`;
|
|
@@ -29327,20 +29350,83 @@ var MergeRequestsClient = class extends GitLabApiClient {
|
|
|
29327
29350
|
* Set auto-merge (MWPS) on a merge request using GraphQL API
|
|
29328
29351
|
* Uses the mergeRequestAccept mutation with a merge strategy
|
|
29329
29352
|
*/
|
|
29330
|
-
async setAutoMerge(projectId, mrIid, sha, strategy = "MERGE_WHEN_CHECKS_PASS") {
|
|
29331
|
-
|
|
29353
|
+
async setAutoMerge(projectId, mrIid, sha, strategy = "MERGE_WHEN_CHECKS_PASS", retries = 3) {
|
|
29354
|
+
let lastError = null;
|
|
29355
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
29356
|
+
const result = await this.fetchGraphQL(SET_AUTO_MERGE_MUTATION, {
|
|
29357
|
+
projectPath: projectId,
|
|
29358
|
+
iid: String(mrIid),
|
|
29359
|
+
sha,
|
|
29360
|
+
strategy
|
|
29361
|
+
});
|
|
29362
|
+
if (result.mergeRequestAccept.errors.length > 0) {
|
|
29363
|
+
const message = result.mergeRequestAccept.errors.join(", ");
|
|
29364
|
+
lastError = new Error(`Failed to set auto-merge: ${message}`);
|
|
29365
|
+
if (attempt < retries && _MergeRequestsClient.isTransientMergeError(message)) {
|
|
29366
|
+
await _MergeRequestsClient.delay(750 * (attempt + 1));
|
|
29367
|
+
continue;
|
|
29368
|
+
}
|
|
29369
|
+
throw lastError;
|
|
29370
|
+
}
|
|
29371
|
+
if (!result.mergeRequestAccept.mergeRequest) {
|
|
29372
|
+
lastError = new Error("Failed to set auto-merge: No merge request returned");
|
|
29373
|
+
if (attempt < retries) {
|
|
29374
|
+
await _MergeRequestsClient.delay(750 * (attempt + 1));
|
|
29375
|
+
continue;
|
|
29376
|
+
}
|
|
29377
|
+
throw lastError;
|
|
29378
|
+
}
|
|
29379
|
+
return result.mergeRequestAccept.mergeRequest;
|
|
29380
|
+
}
|
|
29381
|
+
throw lastError ?? new Error("Failed to set auto-merge: exhausted retries");
|
|
29382
|
+
}
|
|
29383
|
+
/**
|
|
29384
|
+
* True for GitLab merge errors that are transient because mergeability is
|
|
29385
|
+
* still being (re)computed. Such errors clear on their own within a second
|
|
29386
|
+
* or two, so callers should retry rather than treat them as real failures.
|
|
29387
|
+
*/
|
|
29388
|
+
static isTransientMergeError(message) {
|
|
29389
|
+
const m = message.toLowerCase();
|
|
29390
|
+
return m.includes("being checked") || m.includes("being recomputed") || m.includes("cannot be merged") || m.includes("not mergeable") || m.includes("merge failed") || m.includes("try again");
|
|
29391
|
+
}
|
|
29392
|
+
/** Small awaitable delay helper (ms). */
|
|
29393
|
+
static delay(ms) {
|
|
29394
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
29395
|
+
}
|
|
29396
|
+
/**
|
|
29397
|
+
* Override (or clear an override of) requested changes on a merge request.
|
|
29398
|
+
*
|
|
29399
|
+
* When a reviewer formally requests changes, the "Change requests must be
|
|
29400
|
+
* approved by the requesting user" merge check blocks the merge. Setting the
|
|
29401
|
+
* persistent `override_requested_changes` flag downgrades that check from a
|
|
29402
|
+
* blocking failure to a non-blocking warning, which is what the "Bypass"
|
|
29403
|
+
* button in the merge request widget does. This is distinct from
|
|
29404
|
+
* `mergeRequestDestroyRequestedChanges`, which only clears the calling user's
|
|
29405
|
+
* own requested-changes review.
|
|
29406
|
+
*
|
|
29407
|
+
* Requires permission to merge to the target branch (otherwise GitLab
|
|
29408
|
+
* silently ignores the flag).
|
|
29409
|
+
*
|
|
29410
|
+
* @param projectId Project ID or URL-encoded path
|
|
29411
|
+
* @param mrIid Internal ID of the merge request
|
|
29412
|
+
* @param override true to bypass requested changes, false to re-enable the block
|
|
29413
|
+
* @returns The updated merge request (id, iid, title, detailedMergeStatus)
|
|
29414
|
+
*/
|
|
29415
|
+
async overrideRequestedChanges(projectId, mrIid, override = true) {
|
|
29416
|
+
const result = await this.fetchGraphQL(OVERRIDE_REQUESTED_CHANGES_MUTATION, {
|
|
29332
29417
|
projectPath: projectId,
|
|
29333
29418
|
iid: String(mrIid),
|
|
29334
|
-
|
|
29335
|
-
strategy
|
|
29419
|
+
override
|
|
29336
29420
|
});
|
|
29337
|
-
if (result.
|
|
29338
|
-
throw new Error(
|
|
29421
|
+
if (result.mergeRequestUpdate.errors.length > 0) {
|
|
29422
|
+
throw new Error(
|
|
29423
|
+
`Failed to override requested changes: ${result.mergeRequestUpdate.errors.join(", ")}`
|
|
29424
|
+
);
|
|
29339
29425
|
}
|
|
29340
|
-
if (!result.
|
|
29341
|
-
throw new Error("Failed to
|
|
29426
|
+
if (!result.mergeRequestUpdate.mergeRequest) {
|
|
29427
|
+
throw new Error("Failed to override requested changes: No merge request returned");
|
|
29342
29428
|
}
|
|
29343
|
-
return result.
|
|
29429
|
+
return result.mergeRequestUpdate.mergeRequest;
|
|
29344
29430
|
}
|
|
29345
29431
|
/**
|
|
29346
29432
|
* Approve a merge request
|
|
@@ -29388,7 +29474,13 @@ var MergeRequestsClient = class extends GitLabApiClient {
|
|
|
29388
29474
|
* Returns detailed context about what happened
|
|
29389
29475
|
*/
|
|
29390
29476
|
async smartMerge(projectId, mrIid, sha, strategy = "MERGE_WHEN_CHECKS_PASS") {
|
|
29391
|
-
|
|
29477
|
+
let mr = await this.getMergeRequest(projectId, mrIid);
|
|
29478
|
+
if (mr.detailed_merge_status === "checking") {
|
|
29479
|
+
for (let attempt = 0; attempt < 5 && mr.detailed_merge_status === "checking"; attempt++) {
|
|
29480
|
+
await _MergeRequestsClient.delay(1e3 * (attempt + 1));
|
|
29481
|
+
mr = await this.getMergeRequest(projectId, mrIid);
|
|
29482
|
+
}
|
|
29483
|
+
}
|
|
29392
29484
|
const context = {
|
|
29393
29485
|
state: mr.state,
|
|
29394
29486
|
detailedMergeStatus: mr.detailed_merge_status,
|
|
@@ -29427,14 +29519,13 @@ var MergeRequestsClient = class extends GitLabApiClient {
|
|
|
29427
29519
|
};
|
|
29428
29520
|
}
|
|
29429
29521
|
}
|
|
29430
|
-
const autoMergeStatuses = ["ci_still_running", "not_approved"
|
|
29522
|
+
const autoMergeStatuses = ["ci_still_running", "not_approved"];
|
|
29431
29523
|
if (autoMergeStatuses.includes(mr.detailed_merge_status)) {
|
|
29432
29524
|
try {
|
|
29433
29525
|
const result = await this.setAutoMerge(projectId, mrIid, sha, strategy);
|
|
29434
29526
|
const autoMergeMessages = {
|
|
29435
29527
|
ci_still_running: "pipeline passes",
|
|
29436
|
-
not_approved: "approved"
|
|
29437
|
-
checking: "checks complete"
|
|
29528
|
+
not_approved: "approved"
|
|
29438
29529
|
};
|
|
29439
29530
|
return {
|
|
29440
29531
|
action: "auto_merge_enabled",
|
|
@@ -29793,22 +29884,8 @@ var GET_WORK_ITEM_NOTES_QUERY = `
|
|
|
29793
29884
|
}
|
|
29794
29885
|
`;
|
|
29795
29886
|
var CREATE_WORK_ITEM_MUTATION = `
|
|
29796
|
-
mutation createWorkItem(
|
|
29797
|
-
|
|
29798
|
-
$title: String!
|
|
29799
|
-
$workItemTypeId: WorkItemsTypeID!
|
|
29800
|
-
$description: String
|
|
29801
|
-
$labelIds: [LabelID!]
|
|
29802
|
-
$assigneeIds: [UserID!]
|
|
29803
|
-
) {
|
|
29804
|
-
workItemCreate(input: {
|
|
29805
|
-
projectPath: $projectPath
|
|
29806
|
-
title: $title
|
|
29807
|
-
workItemTypeId: $workItemTypeId
|
|
29808
|
-
descriptionWidget: { description: $description }
|
|
29809
|
-
labelsWidget: { labelIds: $labelIds }
|
|
29810
|
-
assigneesWidget: { assigneeIds: $assigneeIds }
|
|
29811
|
-
}) {
|
|
29887
|
+
mutation createWorkItem($input: WorkItemCreateInput!) {
|
|
29888
|
+
workItemCreate(input: $input) {
|
|
29812
29889
|
workItem {
|
|
29813
29890
|
${WORK_ITEM_FIELDS}
|
|
29814
29891
|
}
|
|
@@ -29817,22 +29894,8 @@ var CREATE_WORK_ITEM_MUTATION = `
|
|
|
29817
29894
|
}
|
|
29818
29895
|
`;
|
|
29819
29896
|
var UPDATE_WORK_ITEM_MUTATION = `
|
|
29820
|
-
mutation updateWorkItem(
|
|
29821
|
-
|
|
29822
|
-
$title: String
|
|
29823
|
-
$description: String
|
|
29824
|
-
$stateEvent: WorkItemStateEvent
|
|
29825
|
-
$labelIds: [LabelID!]
|
|
29826
|
-
$assigneeIds: [UserID!]
|
|
29827
|
-
) {
|
|
29828
|
-
workItemUpdate(input: {
|
|
29829
|
-
id: $id
|
|
29830
|
-
title: $title
|
|
29831
|
-
descriptionWidget: { description: $description }
|
|
29832
|
-
stateEvent: $stateEvent
|
|
29833
|
-
labelsWidget: { labelIds: $labelIds }
|
|
29834
|
-
assigneesWidget: { assigneeIds: $assigneeIds }
|
|
29835
|
-
}) {
|
|
29897
|
+
mutation updateWorkItem($input: WorkItemUpdateInput!) {
|
|
29898
|
+
workItemUpdate(input: $input) {
|
|
29836
29899
|
workItem {
|
|
29837
29900
|
${WORK_ITEM_FIELDS}
|
|
29838
29901
|
}
|
|
@@ -29926,14 +29989,21 @@ var WorkItemsClient = class extends GitLabApiClient {
|
|
|
29926
29989
|
async createWorkItem(projectId, options) {
|
|
29927
29990
|
const labelIds = options.labels?.map((l) => toGid("Label", l));
|
|
29928
29991
|
const assigneeIds = options.assignee_ids?.map((id) => toGid("User", id));
|
|
29929
|
-
const
|
|
29992
|
+
const input = {
|
|
29930
29993
|
projectPath: projectId,
|
|
29931
29994
|
title: options.title,
|
|
29932
|
-
workItemTypeId: toGid("WorkItems::Type", options.work_item_type_id)
|
|
29933
|
-
|
|
29934
|
-
|
|
29935
|
-
|
|
29936
|
-
}
|
|
29995
|
+
workItemTypeId: toGid("WorkItems::Type", options.work_item_type_id)
|
|
29996
|
+
};
|
|
29997
|
+
if (options.description !== void 0) {
|
|
29998
|
+
input.descriptionWidget = { description: options.description };
|
|
29999
|
+
}
|
|
30000
|
+
if (labelIds?.length) {
|
|
30001
|
+
input.labelsWidget = { labelIds };
|
|
30002
|
+
}
|
|
30003
|
+
if (assigneeIds?.length) {
|
|
30004
|
+
input.assigneesWidget = { assigneeIds };
|
|
30005
|
+
}
|
|
30006
|
+
const result = await this.fetchGraphQL(CREATE_WORK_ITEM_MUTATION, { input });
|
|
29937
30007
|
if (result.workItemCreate.errors.length > 0) {
|
|
29938
30008
|
throw new Error(`Failed to create work item: ${result.workItemCreate.errors.join(", ")}`);
|
|
29939
30009
|
}
|
|
@@ -29951,14 +30021,23 @@ var WorkItemsClient = class extends GitLabApiClient {
|
|
|
29951
30021
|
const stateEvent = options.state_event ? options.state_event === "close" ? "CLOSE" : "REOPEN" : void 0;
|
|
29952
30022
|
const labelIds = options.labels?.map((l) => toGid("Label", l));
|
|
29953
30023
|
const assigneeIds = options.assignee_ids?.map((id) => toGid("User", id));
|
|
29954
|
-
const
|
|
29955
|
-
|
|
29956
|
-
title
|
|
29957
|
-
|
|
29958
|
-
|
|
29959
|
-
|
|
29960
|
-
|
|
29961
|
-
|
|
30024
|
+
const input = { id: gid };
|
|
30025
|
+
if (options.title !== void 0) {
|
|
30026
|
+
input.title = options.title;
|
|
30027
|
+
}
|
|
30028
|
+
if (options.description !== void 0) {
|
|
30029
|
+
input.descriptionWidget = { description: options.description };
|
|
30030
|
+
}
|
|
30031
|
+
if (stateEvent !== void 0) {
|
|
30032
|
+
input.stateEvent = stateEvent;
|
|
30033
|
+
}
|
|
30034
|
+
if (labelIds?.length) {
|
|
30035
|
+
input.labelsWidget = { addLabelIds: labelIds };
|
|
30036
|
+
}
|
|
30037
|
+
if (assigneeIds?.length) {
|
|
30038
|
+
input.assigneesWidget = { assigneeIds };
|
|
30039
|
+
}
|
|
30040
|
+
const result = await this.fetchGraphQL(UPDATE_WORK_ITEM_MUTATION, { input });
|
|
29962
30041
|
if (result.workItemUpdate.errors.length > 0) {
|
|
29963
30042
|
throw new Error(`Failed to update work item: ${result.workItemUpdate.errors.join(", ")}`);
|
|
29964
30043
|
}
|
|
@@ -31736,6 +31815,29 @@ Always provide sha when you want to ensure you're merging the exact code that wa
|
|
|
31736
31815
|
return JSON.stringify(result, null, 2);
|
|
31737
31816
|
}
|
|
31738
31817
|
}),
|
|
31818
|
+
gitlab_override_requested_changes: tool({
|
|
31819
|
+
description: `Bypass (or re-enable) a "requested changes" merge block on a merge request.
|
|
31820
|
+
|
|
31821
|
+
When a reviewer formally requests changes, the merge check "Change requests must be approved by the requesting user" blocks the merge (detailed_merge_status: requested_changes). This tool sets the persistent override_requested_changes flag, which is exactly what the "Bypass" button in the MR widget does: it downgrades that check from a blocking failure to a non-blocking warning so the MR can be merged.
|
|
31822
|
+
|
|
31823
|
+
Use this when the requesting reviewer is unavailable (e.g. on PTO) and their concerns have been addressed. The acting user must have permission to merge to the target branch, otherwise GitLab silently ignores the flag.
|
|
31824
|
+
|
|
31825
|
+
Note: this is NOT the same as clearing your own requested-changes review. It overrides another user's request. Set override=false to restore the block.`,
|
|
31826
|
+
args: {
|
|
31827
|
+
project_id: z.string().describe("The project ID or URL-encoded path"),
|
|
31828
|
+
mr_iid: z.number().describe("The internal ID of the merge request"),
|
|
31829
|
+
override: z.boolean().optional().describe("true (default) to bypass requested changes, false to restore the block")
|
|
31830
|
+
},
|
|
31831
|
+
execute: async (args, _ctx) => {
|
|
31832
|
+
const client = getGitLabClient();
|
|
31833
|
+
const result = await client.overrideRequestedChanges(
|
|
31834
|
+
args.project_id,
|
|
31835
|
+
args.mr_iid,
|
|
31836
|
+
args.override ?? true
|
|
31837
|
+
);
|
|
31838
|
+
return JSON.stringify(result, null, 2);
|
|
31839
|
+
}
|
|
31840
|
+
}),
|
|
31739
31841
|
gitlab_approve_merge_request: tool({
|
|
31740
31842
|
description: `Approve a merge request.
|
|
31741
31843
|
Adds the current user's approval to the merge request.
|
|
@@ -34075,7 +34177,7 @@ async function main() {
|
|
|
34075
34177
|
...auditTools,
|
|
34076
34178
|
...awardEmojiTools
|
|
34077
34179
|
};
|
|
34078
|
-
const version2 = true ? "2.
|
|
34180
|
+
const version2 = true ? "2.7.0" : "0.0.0";
|
|
34079
34181
|
const server = new McpServer({ name: "gitlab", version: version2 });
|
|
34080
34182
|
adaptToolsToMcp(server, allTools);
|
|
34081
34183
|
const transport = new StdioServerTransport();
|