opencode-gitlab-plugin 2.0.0 → 2.0.1

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,14 @@
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.0.1](https://gitlab.com/vglafirov/opencode-gitlab-plugin/compare/v2.0.0...v2.0.1) (2026-03-22)
6
+
7
+
8
+ ### 🐛 Bug Fixes
9
+
10
+ * **work-items:** address review comments ([0173f25](https://gitlab.com/vglafirov/opencode-gitlab-plugin/commit/0173f25afbecf9b01a61a0239a4112c22e96864f))
11
+ * **work-items:** switch to GraphQL API to resolve iid-vs-id 404 bug ([fcd3417](https://gitlab.com/vglafirov/opencode-gitlab-plugin/commit/fcd3417792e9fcea1ef0a08de2e09d483e769e25))
12
+
5
13
  ## [2.0.0](https://gitlab.com/vglafirov/opencode-gitlab-plugin/compare/v1.9.0...v2.0.0) (2026-03-02)
6
14
 
7
15
 
package/dist/index.js CHANGED
@@ -718,63 +718,356 @@ var IssuesClient = class extends GitLabApiClient {
718
718
  };
719
719
 
720
720
  // src/client/work-items.ts
721
+ function toGid(type, id) {
722
+ return `gid://gitlab/${type}/${id}`;
723
+ }
724
+ var WORK_ITEM_LIST_FIELDS = `
725
+ id
726
+ iid
727
+ title
728
+ description
729
+ state
730
+ createdAt
731
+ updatedAt
732
+ workItemType {
733
+ id
734
+ name
735
+ }
736
+ author {
737
+ id
738
+ username
739
+ name
740
+ }
741
+ assignees {
742
+ nodes {
743
+ id
744
+ username
745
+ name
746
+ }
747
+ }
748
+ labels {
749
+ nodes {
750
+ id
751
+ title
752
+ color
753
+ }
754
+ }
755
+ webUrl
756
+ `;
757
+ var GET_WORK_ITEM_QUERY = `
758
+ query getWorkItem($fullPath: ID!, $iid: String!) {
759
+ workspace: project(fullPath: $fullPath) {
760
+ workItem(iid: $iid) {
761
+ id
762
+ iid
763
+ title
764
+ description
765
+ state
766
+ createdAt
767
+ updatedAt
768
+ closedAt
769
+ confidential
770
+ workItemType {
771
+ id
772
+ name
773
+ }
774
+ author {
775
+ id
776
+ username
777
+ name
778
+ }
779
+ assignees {
780
+ nodes {
781
+ id
782
+ username
783
+ name
784
+ }
785
+ }
786
+ labels {
787
+ nodes {
788
+ id
789
+ title
790
+ color
791
+ }
792
+ }
793
+ webUrl
794
+ }
795
+ }
796
+ }
797
+ `;
798
+ var LIST_WORK_ITEMS_QUERY = `
799
+ query listWorkItems(
800
+ $fullPath: ID!
801
+ $state: WorkItemState
802
+ $search: String
803
+ $types: [WorkItemTypeEnum!]
804
+ $labelNames: [String!]
805
+ $first: Int
806
+ ) {
807
+ workspace: project(fullPath: $fullPath) {
808
+ workItems(
809
+ state: $state
810
+ search: $search
811
+ types: $types
812
+ labelName: $labelNames
813
+ first: $first
814
+ ) {
815
+ nodes {
816
+ ${WORK_ITEM_LIST_FIELDS}
817
+ }
818
+ }
819
+ }
820
+ }
821
+ `;
822
+ var LIST_GROUP_WORK_ITEMS_QUERY = `
823
+ query listGroupWorkItems(
824
+ $fullPath: ID!
825
+ $state: WorkItemState
826
+ $search: String
827
+ $types: [WorkItemTypeEnum!]
828
+ $labelNames: [String!]
829
+ $first: Int
830
+ ) {
831
+ workspace: group(fullPath: $fullPath) {
832
+ workItems(
833
+ state: $state
834
+ search: $search
835
+ types: $types
836
+ labelName: $labelNames
837
+ first: $first
838
+ ) {
839
+ nodes {
840
+ ${WORK_ITEM_LIST_FIELDS}
841
+ }
842
+ }
843
+ }
844
+ }
845
+ `;
846
+ var GET_WORK_ITEM_NOTES_QUERY = `
847
+ query getWorkItemNotes($fullPath: ID!, $iid: String!) {
848
+ workspace: project(fullPath: $fullPath) {
849
+ workItem(iid: $iid) {
850
+ id
851
+ iid
852
+ notes {
853
+ nodes {
854
+ id
855
+ body
856
+ createdAt
857
+ updatedAt
858
+ author {
859
+ id
860
+ username
861
+ name
862
+ }
863
+ system
864
+ resolvable
865
+ resolved
866
+ }
867
+ }
868
+ }
869
+ }
870
+ }
871
+ `;
872
+ var CREATE_WORK_ITEM_MUTATION = `
873
+ mutation createWorkItem(
874
+ $projectPath: ID!
875
+ $title: String!
876
+ $workItemTypeId: WorkItemsTypeID!
877
+ $description: String
878
+ $labelIds: [LabelID!]
879
+ $assigneeIds: [UserID!]
880
+ ) {
881
+ workItemCreate(input: {
882
+ projectPath: $projectPath
883
+ title: $title
884
+ workItemTypeId: $workItemTypeId
885
+ descriptionWidget: { description: $description }
886
+ labelsWidget: { labelIds: $labelIds }
887
+ assigneesWidget: { assigneeIds: $assigneeIds }
888
+ }) {
889
+ workItem {
890
+ id
891
+ iid
892
+ title
893
+ description
894
+ state
895
+ createdAt
896
+ updatedAt
897
+ workItemType {
898
+ id
899
+ name
900
+ }
901
+ author {
902
+ id
903
+ username
904
+ name
905
+ }
906
+ webUrl
907
+ }
908
+ errors
909
+ }
910
+ }
911
+ `;
912
+ var UPDATE_WORK_ITEM_MUTATION = `
913
+ mutation updateWorkItem(
914
+ $id: WorkItemID!
915
+ $title: String
916
+ $description: String
917
+ $stateEvent: WorkItemStateEvent
918
+ $labelIds: [LabelID!]
919
+ $assigneeIds: [UserID!]
920
+ ) {
921
+ workItemUpdate(input: {
922
+ id: $id
923
+ title: $title
924
+ descriptionWidget: { description: $description }
925
+ stateEvent: $stateEvent
926
+ labelsWidget: { labelIds: $labelIds }
927
+ assigneesWidget: { assigneeIds: $assigneeIds }
928
+ }) {
929
+ workItem {
930
+ id
931
+ iid
932
+ title
933
+ description
934
+ state
935
+ updatedAt
936
+ workItemType {
937
+ id
938
+ name
939
+ }
940
+ webUrl
941
+ }
942
+ errors
943
+ }
944
+ }
945
+ `;
946
+ var CREATE_NOTE_MUTATION = `
947
+ mutation createWorkItemNote($noteableId: NoteableID!, $body: String!) {
948
+ createNote(input: { noteableId: $noteableId, body: $body }) {
949
+ note {
950
+ id
951
+ body
952
+ createdAt
953
+ author {
954
+ id
955
+ username
956
+ name
957
+ }
958
+ }
959
+ errors
960
+ }
961
+ }
962
+ `;
963
+ var GET_WORK_ITEM_GID_QUERY = `
964
+ query getWorkItemGid($fullPath: ID!, $iid: String!) {
965
+ workspace: project(fullPath: $fullPath) {
966
+ workItem(iid: $iid) {
967
+ id
968
+ }
969
+ }
970
+ }
971
+ `;
721
972
  var WorkItemsClient = class extends GitLabApiClient {
722
- async getWorkItem(projectId, workItemId) {
723
- const encodedProject = this.encodeProjectId(projectId);
724
- return this.fetch(
725
- "GET",
726
- `/projects/${encodedProject}/work_items/${workItemId}`
727
- );
973
+ async getWorkItem(projectId, workItemIid) {
974
+ const result = await this.fetchGraphQL(GET_WORK_ITEM_QUERY, {
975
+ fullPath: projectId,
976
+ iid: String(workItemIid)
977
+ });
978
+ const workItem = result.workspace?.workItem;
979
+ if (!workItem) {
980
+ throw new Error(`Work item with iid ${workItemIid} not found in project ${projectId}`);
981
+ }
982
+ return workItem;
728
983
  }
729
984
  async listWorkItems(options) {
730
- const params = new URLSearchParams();
731
- params.set("per_page", String(options.limit || 20));
732
- if (options.state) params.set("state", options.state);
733
- if (options.search) params.set("search", options.search);
734
- if (options.labels) params.set("labels", options.labels);
735
- if (options.work_item_type) params.set("type", options.work_item_type);
736
- let path2;
985
+ const variables = {
986
+ state: options.state ? options.state.toUpperCase() : void 0,
987
+ search: options.search,
988
+ types: options.work_item_type ? [options.work_item_type.toUpperCase()] : void 0,
989
+ labelNames: options.labels ? options.labels.split(",").map((l) => l.trim()) : void 0,
990
+ first: options.limit || 20
991
+ };
737
992
  if (options.projectId) {
738
- const encodedProject = this.encodeProjectId(options.projectId);
739
- path2 = `/projects/${encodedProject}/work_items?${params}`;
993
+ const result = await this.fetchGraphQL(LIST_WORK_ITEMS_QUERY, { fullPath: options.projectId, ...variables });
994
+ return result.workspace?.workItems?.nodes ?? [];
740
995
  } else if (options.groupId) {
741
- const encodedGroup = encodeURIComponent(options.groupId);
742
- path2 = `/groups/${encodedGroup}/work_items?${params}`;
996
+ const result = await this.fetchGraphQL(LIST_GROUP_WORK_ITEMS_QUERY, { fullPath: options.groupId, ...variables });
997
+ return result.workspace?.workItems?.nodes ?? [];
743
998
  } else {
744
999
  throw new Error("Either projectId or groupId must be provided");
745
1000
  }
746
- return this.fetch("GET", path2);
747
1001
  }
748
- async getWorkItemNotes(projectId, workItemId) {
749
- const encodedProject = this.encodeProjectId(projectId);
750
- return this.fetch(
751
- "GET",
752
- `/projects/${encodedProject}/work_items/${workItemId}/notes`
753
- );
1002
+ async getWorkItemNotes(projectId, workItemIid) {
1003
+ const result = await this.fetchGraphQL(GET_WORK_ITEM_NOTES_QUERY, {
1004
+ fullPath: projectId,
1005
+ iid: String(workItemIid)
1006
+ });
1007
+ const workItem = result.workspace?.workItem;
1008
+ if (!workItem) {
1009
+ throw new Error(`Work item with iid ${workItemIid} not found in project ${projectId}`);
1010
+ }
1011
+ return workItem.notes?.nodes ?? [];
754
1012
  }
755
1013
  async createWorkItem(projectId, options) {
756
- const encodedProject = this.encodeProjectId(projectId);
757
- return this.fetch(
758
- "POST",
759
- `/projects/${encodedProject}/work_items`,
760
- options
761
- );
1014
+ const labelIds = options.labels?.map((l) => toGid("Label", l));
1015
+ const assigneeIds = options.assignee_ids?.map((id) => toGid("User", id));
1016
+ const result = await this.fetchGraphQL(CREATE_WORK_ITEM_MUTATION, {
1017
+ projectPath: projectId,
1018
+ title: options.title,
1019
+ workItemTypeId: toGid("WorkItems::Type", options.work_item_type_id),
1020
+ description: options.description,
1021
+ labelIds: labelIds?.length ? labelIds : void 0,
1022
+ assigneeIds: assigneeIds?.length ? assigneeIds : void 0
1023
+ });
1024
+ if (result.workItemCreate.errors.length > 0) {
1025
+ throw new Error(`Failed to create work item: ${result.workItemCreate.errors.join(", ")}`);
1026
+ }
1027
+ return result.workItemCreate.workItem;
762
1028
  }
763
- async updateWorkItem(projectId, workItemId, options) {
764
- const encodedProject = this.encodeProjectId(projectId);
765
- return this.fetch(
766
- "PUT",
767
- `/projects/${encodedProject}/work_items/${workItemId}`,
768
- options
769
- );
1029
+ async updateWorkItem(projectId, workItemIid, options) {
1030
+ const gidResult = await this.fetchGraphQL(GET_WORK_ITEM_GID_QUERY, {
1031
+ fullPath: projectId,
1032
+ iid: String(workItemIid)
1033
+ });
1034
+ const gid = gidResult.workspace?.workItem?.id;
1035
+ if (!gid) {
1036
+ throw new Error(`Work item with iid ${workItemIid} not found in project ${projectId}`);
1037
+ }
1038
+ const stateEvent = options.state_event ? options.state_event === "close" ? "CLOSE" : "REOPEN" : void 0;
1039
+ const labelIds = options.labels?.map((l) => toGid("Label", l));
1040
+ const assigneeIds = options.assignee_ids?.map((id) => toGid("User", id));
1041
+ const result = await this.fetchGraphQL(UPDATE_WORK_ITEM_MUTATION, {
1042
+ id: gid,
1043
+ title: options.title,
1044
+ description: options.description,
1045
+ stateEvent,
1046
+ labelIds: labelIds?.length ? labelIds : void 0,
1047
+ assigneeIds: assigneeIds?.length ? assigneeIds : void 0
1048
+ });
1049
+ if (result.workItemUpdate.errors.length > 0) {
1050
+ throw new Error(`Failed to update work item: ${result.workItemUpdate.errors.join(", ")}`);
1051
+ }
1052
+ return result.workItemUpdate.workItem;
770
1053
  }
771
- async createWorkItemNote(projectId, workItemId, body) {
772
- const encodedProject = this.encodeProjectId(projectId);
773
- return this.fetch(
774
- "POST",
775
- `/projects/${encodedProject}/work_items/${workItemId}/notes`,
776
- { body }
777
- );
1054
+ async createWorkItemNote(projectId, workItemIid, body) {
1055
+ const gidResult = await this.fetchGraphQL(GET_WORK_ITEM_GID_QUERY, {
1056
+ fullPath: projectId,
1057
+ iid: String(workItemIid)
1058
+ });
1059
+ const gid = gidResult.workspace?.workItem?.id;
1060
+ if (!gid) {
1061
+ throw new Error(`Work item with iid ${workItemIid} not found in project ${projectId}`);
1062
+ }
1063
+ const result = await this.fetchGraphQL(CREATE_NOTE_MUTATION, {
1064
+ noteableId: gid,
1065
+ body
1066
+ });
1067
+ if (result.createNote.errors.length > 0) {
1068
+ throw new Error(`Failed to create note: ${result.createNote.errors.join(", ")}`);
1069
+ }
1070
+ return result.createNote.note;
778
1071
  }
779
1072
  };
780
1073
 
@@ -3598,8 +3891,10 @@ var workItemTools = {
3598
3891
  description: `Get a single work item (issue, epic, task, etc.).
3599
3892
  Work items are the new unified model for issues, epics, tasks, and other work tracking items in GitLab.`,
3600
3893
  args: {
3601
- project_id: z12.string().describe("The project ID or URL-encoded path"),
3602
- work_item_id: z12.number().describe("The ID of the work item")
3894
+ project_id: z12.string().describe('The project ID or full path (e.g. "gitlab-org/gitlab")'),
3895
+ work_item_id: z12.number().describe(
3896
+ "The IID of the work item \u2014 the number visible in the GitLab URL (e.g. for https://gitlab.com/group/project/-/work_items/52, use 52). This is NOT the global database ID."
3897
+ )
3603
3898
  },
3604
3899
  execute: async (args, _ctx) => {
3605
3900
  const client = getGitLabClient();
@@ -3637,8 +3932,10 @@ Work items include issues, epics, tasks, and other work tracking items.`,
3637
3932
  description: `Get all comments for a work item.
3638
3933
  Returns all notes/comments on the work item in chronological order.`,
3639
3934
  args: {
3640
- project_id: z12.string().describe("The project ID or URL-encoded path"),
3641
- work_item_id: z12.number().describe("The ID of the work item")
3935
+ project_id: z12.string().describe('The project ID or full path (e.g. "gitlab-org/gitlab")'),
3936
+ work_item_id: z12.number().describe(
3937
+ "The IID of the work item \u2014 the number visible in the GitLab URL (e.g. for https://gitlab.com/group/project/-/work_items/52, use 52). This is NOT the global database ID."
3938
+ )
3642
3939
  },
3643
3940
  execute: async (args, _ctx) => {
3644
3941
  const client = getGitLabClient();
@@ -3673,8 +3970,10 @@ Work items are the new unified model for issues, epics, tasks, and other work tr
3673
3970
  description: `Update an existing work item.
3674
3971
  Can update title, description, state, labels, and assignees.`,
3675
3972
  args: {
3676
- project_id: z12.string().describe("The project ID or URL-encoded path"),
3677
- work_item_id: z12.number().describe("The ID of the work item"),
3973
+ project_id: z12.string().describe('The project ID or full path (e.g. "gitlab-org/gitlab")'),
3974
+ work_item_id: z12.number().describe(
3975
+ "The IID of the work item \u2014 the number visible in the GitLab URL (e.g. for https://gitlab.com/group/project/-/work_items/52, use 52). This is NOT the global database ID."
3976
+ ),
3678
3977
  title: z12.string().optional().describe("The new title"),
3679
3978
  description: z12.string().optional().describe("The new description (supports Markdown)"),
3680
3979
  state_event: z12.enum(["close", "reopen"]).optional().describe("Change the state (close or reopen)"),
@@ -3696,8 +3995,10 @@ Can update title, description, state, labels, and assignees.`,
3696
3995
  gitlab_create_work_item_note: tool12({
3697
3996
  description: `Create a comment on a work item.`,
3698
3997
  args: {
3699
- project_id: z12.string().describe("The project ID or URL-encoded path"),
3700
- work_item_id: z12.number().describe("The ID of the work item"),
3998
+ project_id: z12.string().describe('The project ID or full path (e.g. "gitlab-org/gitlab")'),
3999
+ work_item_id: z12.number().describe(
4000
+ "The IID of the work item \u2014 the number visible in the GitLab URL (e.g. for https://gitlab.com/group/project/-/work_items/52, use 52). This is NOT the global database ID."
4001
+ ),
3701
4002
  body: z12.string().describe("The content of the note/comment (supports Markdown)")
3702
4003
  },
3703
4004
  execute: async (args, _ctx) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-gitlab-plugin",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
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",