@gitlab/opencode-gitlab-plugin 1.5.4 → 1.5.6

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/dist/index.js CHANGED
@@ -2086,33 +2086,6 @@ Returns the list of files changed with their diffs.`,
2086
2086
  return JSON.stringify(changes, null, 2);
2087
2087
  }
2088
2088
  }),
2089
- gitlab_list_mr_notes: tool({
2090
- description: `List all notes/comments on a merge request using GraphQL API with pagination support.
2091
- Returns all comments including system notes, code review comments, and general discussion.
2092
- This is easier to read than discussions which have nested structure.
2093
-
2094
- The response includes pagination information (pageInfo) with cursors for fetching additional pages.
2095
- Use 'after' with the 'endCursor' from pageInfo to get the next page.
2096
- Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
2097
- args: {
2098
- project_id: z.string().describe("The project ID or URL-encoded path"),
2099
- mr_iid: z.number().describe("The internal ID of the merge request"),
2100
- first: z.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
2101
- after: z.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
2102
- last: z.number().optional().describe("Number of items to return from the end (for backward pagination)"),
2103
- before: z.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
2104
- },
2105
- execute: async (args, _ctx) => {
2106
- const client = getGitLabClient();
2107
- const result = await client.listMrNotes(args.project_id, args.mr_iid, {
2108
- first: args.first,
2109
- after: args.after,
2110
- last: args.last,
2111
- before: args.before
2112
- });
2113
- return JSON.stringify(result, null, 2);
2114
- }
2115
- }),
2116
2089
  gitlab_create_merge_request: tool({
2117
2090
  description: `Create a new merge request.
2118
2091
  Returns the created merge request with all details.`,
@@ -2330,47 +2303,6 @@ Can filter by state, labels, assignee, milestone.`,
2330
2303
  });
2331
2304
  return JSON.stringify(issues, null, 2);
2332
2305
  }
2333
- }),
2334
- gitlab_list_issue_notes: tool2({
2335
- description: `List all notes/comments on an issue using GraphQL API with pagination support.
2336
- Returns all comments including system notes in chronological order.
2337
-
2338
- The response includes pagination information (pageInfo) with cursors for fetching additional pages.
2339
- Use 'after' with the 'endCursor' from pageInfo to get the next page.
2340
- Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
2341
- args: {
2342
- project_id: z2.string().describe("The project ID or URL-encoded path"),
2343
- issue_iid: z2.number().describe("The internal ID of the issue"),
2344
- first: z2.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
2345
- after: z2.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
2346
- last: z2.number().optional().describe("Number of items to return from the end (for backward pagination)"),
2347
- before: z2.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
2348
- },
2349
- execute: async (args, _ctx) => {
2350
- const client = getGitLabClient();
2351
- const result = await client.listIssueNotes(args.project_id, args.issue_iid, {
2352
- first: args.first,
2353
- after: args.after,
2354
- last: args.last,
2355
- before: args.before
2356
- });
2357
- return JSON.stringify(result, null, 2);
2358
- }
2359
- }),
2360
- gitlab_get_issue_note: tool2({
2361
- description: `Get a single note/comment from an issue by its ID.
2362
- Returns the full details of a specific note including author, body, timestamps, and metadata.
2363
- Useful when you need to retrieve a specific comment without fetching all notes.`,
2364
- args: {
2365
- project_id: z2.string().describe("The project ID or URL-encoded path"),
2366
- issue_iid: z2.number().describe("The internal ID of the issue"),
2367
- note_id: z2.number().describe("The ID of the note to retrieve")
2368
- },
2369
- execute: async (args, _ctx) => {
2370
- const client = getGitLabClient();
2371
- const note = await client.getIssueNote(args.project_id, args.issue_iid, args.note_id);
2372
- return JSON.stringify(note, null, 2);
2373
- }
2374
2306
  })
2375
2307
  };
2376
2308
 
@@ -2514,47 +2446,6 @@ Removes the association between the issue and the epic.`,
2514
2446
  );
2515
2447
  return JSON.stringify(result, null, 2);
2516
2448
  }
2517
- }),
2518
- gitlab_list_epic_notes: tool3({
2519
- description: `List all comments/notes on an epic using GraphQL API with pagination support.
2520
- Returns all comments in chronological order.
2521
-
2522
- The response includes pagination information (pageInfo) with cursors for fetching additional pages.
2523
- Use 'after' with the 'endCursor' from pageInfo to get the next page.
2524
- Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
2525
- args: {
2526
- group_id: z3.string().describe("The group ID or URL-encoded path"),
2527
- epic_iid: z3.number().describe("The internal ID of the epic"),
2528
- first: z3.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
2529
- after: z3.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
2530
- last: z3.number().optional().describe("Number of items to return from the end (for backward pagination)"),
2531
- before: z3.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
2532
- },
2533
- execute: async (args, _ctx) => {
2534
- const client = getGitLabClient();
2535
- const result = await client.listEpicNotes(args.group_id, args.epic_iid, {
2536
- first: args.first,
2537
- after: args.after,
2538
- last: args.last,
2539
- before: args.before
2540
- });
2541
- return JSON.stringify(result, null, 2);
2542
- }
2543
- }),
2544
- gitlab_get_epic_note: tool3({
2545
- description: `Get a single note/comment from an epic by its ID.
2546
- Returns the full details of a specific note including author, body, timestamps, and metadata.
2547
- Useful when you need to retrieve a specific comment without fetching all notes.`,
2548
- args: {
2549
- group_id: z3.string().describe("The group ID or URL-encoded path"),
2550
- epic_iid: z3.number().describe("The internal ID of the epic"),
2551
- note_id: z3.number().describe("The ID of the note to retrieve")
2552
- },
2553
- execute: async (args, _ctx) => {
2554
- const client = getGitLabClient();
2555
- const note = await client.getEpicNote(args.group_id, args.epic_iid, args.note_id);
2556
- return JSON.stringify(note, null, 2);
2557
- }
2558
2449
  })
2559
2450
  };
2560
2451
 
@@ -2843,10 +2734,83 @@ This is different from discussions - it returns individual comments in a flat st
2843
2734
  // src/tools/search.ts
2844
2735
  import { tool as tool6 } from "@opencode-ai/plugin";
2845
2736
  var z6 = tool6.schema;
2737
+ var GENERIC_SEARCH_SCOPES = ["projects", "issues", "merge_requests", "blobs"];
2738
+ var SPECIALIZED_SCOPES = [
2739
+ "milestones",
2740
+ "users",
2741
+ "commits",
2742
+ "notes",
2743
+ "wiki_blobs",
2744
+ "group_projects"
2745
+ ];
2746
+ var ALL_SCOPES = [...GENERIC_SEARCH_SCOPES, ...SPECIALIZED_SCOPES];
2747
+ var REF_SUPPORTED_SCOPES = ["commits", "wiki_blobs"];
2748
+ var STATE_SUPPORTED_SCOPES = ["milestones"];
2749
+ function validationError(param, scope) {
2750
+ return new Error(`Missing required parameter: '${param}' is required for scope '${scope}'`);
2751
+ }
2752
+ function invalidParamError(param, validScopes) {
2753
+ return new Error(
2754
+ `Invalid parameter: '${param}' is only valid for scopes: ${validScopes.join(", ")}`
2755
+ );
2756
+ }
2757
+ function validateSearchParams(scope, args) {
2758
+ switch (scope) {
2759
+ case "notes":
2760
+ if (!args.project_id) throw validationError("project_id", scope);
2761
+ break;
2762
+ case "group_projects":
2763
+ if (!args.group_id) throw validationError("group_id", scope);
2764
+ break;
2765
+ }
2766
+ if (args.ref && !REF_SUPPORTED_SCOPES.includes(scope)) {
2767
+ throw invalidParamError("ref", REF_SUPPORTED_SCOPES);
2768
+ }
2769
+ if (args.state && !STATE_SUPPORTED_SCOPES.includes(scope)) {
2770
+ throw invalidParamError("state", STATE_SUPPORTED_SCOPES);
2771
+ }
2772
+ }
2846
2773
  var searchTools = {
2774
+ /**
2775
+ * Unified search across all GitLab resources
2776
+ *
2777
+ * @example
2778
+ * // Search for issues in a project
2779
+ * gitlab_search({ scope: "issues", search: "bug", project_id: "my-group/project" })
2780
+ *
2781
+ * @example
2782
+ * // Search commits on specific branch
2783
+ * gitlab_search({ scope: "commits", search: "fix", ref: "main" })
2784
+ *
2785
+ * @example
2786
+ * // Search for projects within a group
2787
+ * gitlab_search({ scope: "group_projects", group_id: "gitlab-org", search: "runner" })
2788
+ *
2789
+ * @example
2790
+ * // Search notes/comments in a project
2791
+ * gitlab_search({ scope: "notes", search: "LGTM", project_id: "my-group/project" })
2792
+ */
2847
2793
  gitlab_search: tool6({
2848
- description: `Search across GitLab for various resources.
2849
- Scopes: projects, issues, merge_requests, milestones, users, blobs (code), commits, notes, wiki_blobs`,
2794
+ description: `Search across GitLab for various resources with scope-specific options.
2795
+
2796
+ Scopes and their requirements:
2797
+ - projects: Search projects by name/description
2798
+ - issues: Search issues by title/description
2799
+ - merge_requests: Search MRs by title/description
2800
+ - milestones: Search milestones (supports state filter)
2801
+ - users: Search users by name/email
2802
+ - blobs: Search file content (code search)
2803
+ - commits: Search commits by message/author/SHA (supports ref filter)
2804
+ - notes: Search comments/notes (requires project_id)
2805
+ - wiki_blobs: Search wiki content (supports ref filter)
2806
+ - group_projects: Search projects within a group (requires group_id)
2807
+
2808
+ Examples:
2809
+ - Issues: scope="issues", search="bug", project_id="my-group/my-project"
2810
+ - Code: scope="blobs", search="function calculateTotal"
2811
+ - Commits: scope="commits", search="fix login", ref="main"
2812
+ - Group projects: scope="group_projects", group_id="gitlab-org", search="runner"
2813
+ - Notes: scope="notes", search="LGTM", project_id="my-group/my-project"`,
2850
2814
  args: {
2851
2815
  scope: z6.enum([
2852
2816
  "projects",
@@ -2857,238 +2821,104 @@ Scopes: projects, issues, merge_requests, milestones, users, blobs (code), commi
2857
2821
  "blobs",
2858
2822
  "commits",
2859
2823
  "notes",
2860
- "wiki_blobs"
2861
- ]).describe("The scope of the search"),
2824
+ "wiki_blobs",
2825
+ "group_projects"
2826
+ ]).describe("The type of resource to search"),
2862
2827
  search: z6.string().describe("The search query"),
2863
- project_id: z6.string().optional().describe("Limit search to a specific project (optional)"),
2864
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
2865
- },
2866
- execute: async (args, _ctx) => {
2867
- const client = getGitLabClient();
2868
- const results = await client.search(args.scope, args.search, args.project_id, args.limit);
2869
- return JSON.stringify(results, null, 2);
2870
- }
2871
- }),
2872
- gitlab_issue_search: tool6({
2873
- description: `Search issues by keyword across GitLab.
2874
- Specialized search for issues with better filtering than the generic search.`,
2875
- args: {
2876
- search: z6.string().describe("The search query"),
2877
- project_id: z6.string().optional().describe("Limit search to a specific project (optional)"),
2878
- state: z6.enum(["opened", "closed", "all"]).optional().describe("Filter by issue state"),
2879
- labels: z6.string().optional().describe("Comma-separated list of labels to filter by"),
2880
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
2881
- },
2882
- execute: async (args, _ctx) => {
2883
- const client = getGitLabClient();
2884
- const results = await client.search("issues", args.search, args.project_id, args.limit);
2885
- return JSON.stringify(results, null, 2);
2886
- }
2887
- }),
2888
- gitlab_blob_search: tool6({
2889
- description: `Search file content in repositories.
2890
- Search for code/text within files across projects.`,
2891
- args: {
2892
- search: z6.string().describe("The search query (code/text to find)"),
2893
- project_id: z6.string().optional().describe("Limit search to a specific project (optional)"),
2894
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
2895
- },
2896
- execute: async (args, _ctx) => {
2897
- const client = getGitLabClient();
2898
- const results = await client.search("blobs", args.search, args.project_id, args.limit);
2899
- return JSON.stringify(results, null, 2);
2900
- }
2901
- }),
2902
- gitlab_merge_request_search: tool6({
2903
- description: `Search merge requests by keyword.
2904
- Specialized search for merge requests with better filtering than the generic search.`,
2905
- args: {
2906
- search: z6.string().describe("The search query"),
2907
- project_id: z6.string().optional().describe("Limit search to a specific project (optional)"),
2908
- state: z6.enum(["opened", "closed", "merged", "all"]).optional().describe("Filter by MR state"),
2909
- labels: z6.string().optional().describe("Comma-separated list of labels to filter by"),
2910
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
2911
- },
2912
- execute: async (args, _ctx) => {
2913
- const client = getGitLabClient();
2914
- const results = await client.search(
2915
- "merge_requests",
2916
- args.search,
2917
- args.project_id,
2918
- args.limit
2919
- );
2920
- return JSON.stringify(results, null, 2);
2921
- }
2922
- }),
2923
- gitlab_commit_search: tool6({
2924
- description: `Search for commits in a project or across GitLab.
2925
- Returns commits matching the search query with commit details including SHA, title, message, author, and dates.
2926
-
2927
- Supports searching by:
2928
- - Commit message content
2929
- - Author name or email
2930
- - Commit SHA (partial or full)
2931
-
2932
- Note: Advanced search features (Premium/Ultimate) provide better commit search capabilities.`,
2933
- args: {
2934
- search: z6.string().describe("The search query (commit message, author, or SHA)"),
2935
- project_id: z6.string().optional().describe("Limit search to a specific project (optional)"),
2936
- ref: z6.string().optional().describe("Branch or tag name to search on (defaults to default branch)"),
2937
- order_by: z6.enum(["created_at"]).optional().describe("Order results by created_at (default: created_at desc)"),
2938
- sort: z6.enum(["asc", "desc"]).optional().describe("Sort order (asc or desc)"),
2939
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
2828
+ // Context parameters
2829
+ project_id: z6.string().optional().describe("Limit search to a specific project. Required for notes scope"),
2830
+ group_id: z6.string().optional().describe("Group ID or path. Required for group_projects scope"),
2831
+ // Common options
2832
+ limit: z6.number().optional().describe("Maximum number of results (default: 20)"),
2833
+ order_by: z6.enum(["created_at"]).optional().describe(
2834
+ "Order results by field (for milestones, users, commits, notes, wiki_blobs, group_projects)"
2835
+ ),
2836
+ sort: z6.enum(["asc", "desc"]).optional().describe("Sort order (for milestones, users, commits, notes, wiki_blobs, group_projects)"),
2837
+ // Scope-specific options
2838
+ ref: z6.string().optional().describe("Branch or tag name (for commits, wiki_blobs scopes)"),
2839
+ state: z6.enum(["active", "closed", "all"]).optional().describe("Filter by state (for milestones scope)")
2940
2840
  },
2941
2841
  execute: async (args, _ctx) => {
2942
- const client = getGitLabClient();
2943
- const results = await client.searchCommits(args.search, args.project_id, {
2842
+ validateSearchParams(args.scope, {
2843
+ project_id: args.project_id,
2844
+ group_id: args.group_id,
2944
2845
  ref: args.ref,
2945
- order_by: args.order_by,
2946
- sort: args.sort,
2947
- limit: args.limit
2948
- });
2949
- return JSON.stringify(results, null, 2);
2950
- }
2951
- }),
2952
- gitlab_group_project_search: tool6({
2953
- description: `Search for projects within a specific group.
2954
- Returns projects matching the search query within the specified group and its subgroups.
2955
-
2956
- Useful for:
2957
- - Finding projects by name or description within a group
2958
- - Discovering projects in large group hierarchies
2959
- - Filtering group projects by keywords`,
2960
- args: {
2961
- group_id: z6.string().describe("The group ID or URL-encoded path"),
2962
- search: z6.string().describe("The search query (project name or description)"),
2963
- order_by: z6.enum(["created_at"]).optional().describe("Order results by created_at (default: created_at desc)"),
2964
- sort: z6.enum(["asc", "desc"]).optional().describe("Sort order (asc or desc)"),
2965
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
2966
- },
2967
- execute: async (args, _ctx) => {
2968
- const client = getGitLabClient();
2969
- const results = await client.searchGroupProjects(args.group_id, args.search, {
2970
- order_by: args.order_by,
2971
- sort: args.sort,
2972
- limit: args.limit
2973
- });
2974
- return JSON.stringify(results, null, 2);
2975
- }
2976
- }),
2977
- gitlab_milestone_search: tool6({
2978
- description: `Search for milestones in a project or across GitLab.
2979
- Returns milestones matching the search query with details including title, description, state, and dates.
2980
-
2981
- Useful for:
2982
- - Finding milestones by title or description
2983
- - Discovering active or closed milestones
2984
- - Planning and tracking project milestones`,
2985
- args: {
2986
- search: z6.string().describe("The search query (milestone title or description)"),
2987
- project_id: z6.string().optional().describe("Limit search to a specific project (optional)"),
2988
- state: z6.enum(["active", "closed", "all"]).optional().describe("Filter by milestone state"),
2989
- order_by: z6.enum(["created_at"]).optional().describe("Order results by created_at (default: created_at desc)"),
2990
- sort: z6.enum(["asc", "desc"]).optional().describe("Sort order (asc or desc)"),
2991
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
2992
- },
2993
- execute: async (args, _ctx) => {
2994
- const client = getGitLabClient();
2995
- const results = await client.searchMilestones(args.search, args.project_id, {
2996
- state: args.state,
2997
- order_by: args.order_by,
2998
- sort: args.sort,
2999
- limit: args.limit
3000
- });
3001
- return JSON.stringify(results, null, 2);
3002
- }
3003
- }),
3004
- gitlab_note_search: tool6({
3005
- description: `Search for notes/comments in a project.
3006
- Returns notes (comments) matching the search query from issues, merge requests, commits, and snippets.
3007
-
3008
- Useful for:
3009
- - Finding specific comments or discussions
3010
- - Tracking feedback and review comments
3011
- - Searching for mentions or keywords in conversations
3012
-
3013
- Note: This scope requires Premium or Ultimate tier with advanced search enabled.`,
3014
- args: {
3015
- search: z6.string().describe("The search query (comment text)"),
3016
- project_id: z6.string().describe("The project ID or URL-encoded path to search in"),
3017
- order_by: z6.enum(["created_at"]).optional().describe("Order results by created_at (default: created_at desc)"),
3018
- sort: z6.enum(["asc", "desc"]).optional().describe("Sort order (asc or desc)"),
3019
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
3020
- },
3021
- execute: async (args, _ctx) => {
3022
- const client = getGitLabClient();
3023
- const results = await client.searchNotes(args.search, args.project_id, {
3024
- order_by: args.order_by,
3025
- sort: args.sort,
3026
- limit: args.limit
2846
+ state: args.state
3027
2847
  });
3028
- return JSON.stringify(results, null, 2);
3029
- }
3030
- }),
3031
- gitlab_user_search: tool6({
3032
- description: `Search for users by name or email across GitLab.
3033
- Returns users matching the search query with details including username, name, state, and avatar.
3034
-
3035
- Useful for:
3036
- - Finding users to assign to issues or merge requests
3037
- - Looking up user information by name or email
3038
- - Discovering team members and collaborators`,
3039
- args: {
3040
- search: z6.string().describe("The search query (user name or email)"),
3041
- project_id: z6.string().optional().describe("Limit search to project members (optional)"),
3042
- order_by: z6.enum(["created_at"]).optional().describe("Order results by created_at (default: created_at desc)"),
3043
- sort: z6.enum(["asc", "desc"]).optional().describe("Sort order (asc or desc)"),
3044
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
3045
- },
3046
- execute: async (args, _ctx) => {
3047
- const client = getGitLabClient();
3048
- const results = await client.searchUsers(args.search, args.project_id, {
3049
- order_by: args.order_by,
3050
- sort: args.sort,
3051
- limit: args.limit
3052
- });
3053
- return JSON.stringify(results, null, 2);
3054
- }
3055
- }),
3056
- gitlab_wiki_blob_search: tool6({
3057
- description: `Search for wiki content (blobs) in a project or across GitLab.
3058
- Returns wiki pages matching the search query with content snippets and file information.
3059
-
3060
- Supports advanced filters (use in search query):
3061
- - filename:some_name* - Filter by filename with wildcards
3062
- - path:some/path* - Filter by path with wildcards
3063
- - extension:md - Filter by file extension
3064
-
3065
- Examples:
3066
- - "installation" - Search for "installation" in wiki content
3067
- - "setup filename:getting-started*" - Search in files starting with "getting-started"
3068
- - "api extension:md" - Search for "api" in markdown files
3069
-
3070
- Note: Advanced search (Premium/Ultimate) provides better wiki search capabilities.`,
3071
- args: {
3072
- search: z6.string().describe(
3073
- "The search query (supports filters: filename:, path:, extension: with wildcards)"
3074
- ),
3075
- project_id: z6.string().optional().describe("Limit search to a specific project (optional)"),
3076
- ref: z6.string().optional().describe("Branch or tag name to search on (defaults to default branch)"),
3077
- order_by: z6.enum(["created_at"]).optional().describe("Order results by created_at (default: created_at desc)"),
3078
- sort: z6.enum(["asc", "desc"]).optional().describe("Sort order (asc or desc)"),
3079
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
3080
- },
3081
- execute: async (args, _ctx) => {
3082
2848
  const client = getGitLabClient();
3083
- const results = await client.searchWikiBlobs(args.search, args.project_id, {
3084
- ref: args.ref,
2849
+ const commonOptions = {
3085
2850
  order_by: args.order_by,
3086
2851
  sort: args.sort,
3087
2852
  limit: args.limit
3088
- });
3089
- return JSON.stringify(results, null, 2);
2853
+ };
2854
+ switch (args.scope) {
2855
+ // Generic search API scopes
2856
+ case "projects":
2857
+ case "issues":
2858
+ case "merge_requests":
2859
+ case "blobs":
2860
+ return JSON.stringify(
2861
+ await client.search(args.scope, args.search, args.project_id, args.limit),
2862
+ null,
2863
+ 2
2864
+ );
2865
+ // Specialized scopes with dedicated client methods
2866
+ case "milestones":
2867
+ return JSON.stringify(
2868
+ await client.searchMilestones(args.search, args.project_id, {
2869
+ ...commonOptions,
2870
+ state: args.state
2871
+ }),
2872
+ null,
2873
+ 2
2874
+ );
2875
+ case "users":
2876
+ return JSON.stringify(
2877
+ await client.searchUsers(args.search, args.project_id, commonOptions),
2878
+ null,
2879
+ 2
2880
+ );
2881
+ case "commits":
2882
+ return JSON.stringify(
2883
+ await client.searchCommits(args.search, args.project_id, {
2884
+ ...commonOptions,
2885
+ ref: args.ref
2886
+ }),
2887
+ null,
2888
+ 2
2889
+ );
2890
+ case "notes":
2891
+ return JSON.stringify(
2892
+ await client.searchNotes(args.search, args.project_id, commonOptions),
2893
+ null,
2894
+ 2
2895
+ );
2896
+ case "wiki_blobs":
2897
+ return JSON.stringify(
2898
+ await client.searchWikiBlobs(args.search, args.project_id, {
2899
+ ...commonOptions,
2900
+ ref: args.ref
2901
+ }),
2902
+ null,
2903
+ 2
2904
+ );
2905
+ case "group_projects":
2906
+ return JSON.stringify(
2907
+ await client.searchGroupProjects(args.group_id, args.search, commonOptions),
2908
+ null,
2909
+ 2
2910
+ );
2911
+ default:
2912
+ throw new Error(
2913
+ `Invalid scope '${args.scope}'. Must be one of: ${ALL_SCOPES.join(", ")}`
2914
+ );
2915
+ }
3090
2916
  }
3091
2917
  }),
2918
+ /**
2919
+ * Search GitLab official documentation
2920
+ * Separate tool because it uses a completely different API (docs.gitlab.com)
2921
+ */
3092
2922
  gitlab_documentation_search: tool6({
3093
2923
  description: `Search GitLab official documentation at docs.gitlab.com.
3094
2924
  Returns relevant documentation pages matching the search query.
@@ -3320,43 +3150,11 @@ Requires Developer role or higher.`,
3320
3150
  })
3321
3151
  };
3322
3152
 
3323
- // src/tools/snippets.ts
3153
+ // src/tools/todos.ts
3324
3154
  import { tool as tool9 } from "@opencode-ai/plugin";
3325
3155
  var z9 = tool9.schema;
3326
- var snippetTools = {
3327
- gitlab_list_snippet_notes: tool9({
3328
- description: `List all notes/comments on a project snippet using GraphQL API with pagination support.
3329
- Returns all comments including system notes in chronological order.
3330
-
3331
- The response includes pagination information (pageInfo) with cursors for fetching additional pages.
3332
- Use 'after' with the 'endCursor' from pageInfo to get the next page.
3333
- Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
3334
- args: {
3335
- project_id: z9.string().describe("The project ID or URL-encoded path"),
3336
- snippet_id: z9.number().describe("The ID of the snippet"),
3337
- first: z9.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
3338
- after: z9.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
3339
- last: z9.number().optional().describe("Number of items to return from the end (for backward pagination)"),
3340
- before: z9.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
3341
- },
3342
- execute: async (args, _ctx) => {
3343
- const client = getGitLabClient();
3344
- const result = await client.listSnippetNotes(args.project_id, args.snippet_id, {
3345
- first: args.first,
3346
- after: args.after,
3347
- last: args.last,
3348
- before: args.before
3349
- });
3350
- return JSON.stringify(result, null, 2);
3351
- }
3352
- })
3353
- };
3354
-
3355
- // src/tools/todos.ts
3356
- import { tool as tool10 } from "@opencode-ai/plugin";
3357
- var z10 = tool10.schema;
3358
3156
  var todoTools = {
3359
- gitlab_list_todos: tool10({
3157
+ gitlab_list_todos: tool9({
3360
3158
  description: `List TODO items for the current user using GraphQL API with pagination support.
3361
3159
  Returns a list of pending or done TODO items assigned to the authenticated user.
3362
3160
  TODOs are created when you are assigned to an issue/MR, mentioned in a comment, or when someone requests your review.
@@ -3365,7 +3163,7 @@ The response includes pagination information (pageInfo) with cursors for fetchin
3365
3163
  Use 'after' with the 'endCursor' from pageInfo to get the next page.
3366
3164
  Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
3367
3165
  args: {
3368
- action: z10.enum([
3166
+ action: z9.enum([
3369
3167
  "assigned",
3370
3168
  "mentioned",
3371
3169
  "build_failed",
@@ -3376,15 +3174,15 @@ Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
3376
3174
  "merge_train_removed",
3377
3175
  "review_requested"
3378
3176
  ]).optional().describe("Filter by action type"),
3379
- author_id: z10.number().optional().describe("Filter by author ID"),
3380
- project_id: z10.string().optional().describe("Filter by project ID or path"),
3381
- group_id: z10.string().optional().describe("Filter by group ID"),
3382
- state: z10.enum(["pending", "done"]).optional().describe("Filter by state (default: pending)"),
3383
- type: z10.enum(["Issue", "MergeRequest", "DesignManagement::Design", "Alert", "Epic", "Commit"]).optional().describe("Filter by target type"),
3384
- first: z10.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
3385
- after: z10.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
3386
- last: z10.number().optional().describe("Number of items to return from the end (for backward pagination)"),
3387
- before: z10.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
3177
+ author_id: z9.number().optional().describe("Filter by author ID"),
3178
+ project_id: z9.string().optional().describe("Filter by project ID or path"),
3179
+ group_id: z9.string().optional().describe("Filter by group ID"),
3180
+ state: z9.enum(["pending", "done"]).optional().describe("Filter by state (default: pending)"),
3181
+ type: z9.enum(["Issue", "MergeRequest", "DesignManagement::Design", "Alert", "Epic", "Commit"]).optional().describe("Filter by target type"),
3182
+ first: z9.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
3183
+ after: z9.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
3184
+ last: z9.number().optional().describe("Number of items to return from the end (for backward pagination)"),
3185
+ before: z9.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
3388
3186
  },
3389
3187
  execute: async (args, _ctx) => {
3390
3188
  const client = getGitLabClient();
@@ -3403,11 +3201,11 @@ Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
3403
3201
  return JSON.stringify(result, null, 2);
3404
3202
  }
3405
3203
  }),
3406
- gitlab_mark_todo_done: tool10({
3204
+ gitlab_mark_todo_done: tool9({
3407
3205
  description: `Mark a specific TODO item as done.
3408
3206
  Use this to mark a single TODO as completed.`,
3409
3207
  args: {
3410
- todo_id: z10.number().describe("The ID of the TODO item to mark as done")
3208
+ todo_id: z9.number().describe("The ID of the TODO item to mark as done")
3411
3209
  },
3412
3210
  execute: async (args, _ctx) => {
3413
3211
  const client = getGitLabClient();
@@ -3415,7 +3213,7 @@ Use this to mark a single TODO as completed.`,
3415
3213
  return JSON.stringify(result, null, 2);
3416
3214
  }
3417
3215
  }),
3418
- gitlab_mark_all_todos_done: tool10({
3216
+ gitlab_mark_all_todos_done: tool9({
3419
3217
  description: `Mark all TODO items as done.
3420
3218
  Use this to mark all pending TODOs for the current user as completed.`,
3421
3219
  args: {},
@@ -3425,7 +3223,7 @@ Use this to mark all pending TODOs for the current user as completed.`,
3425
3223
  return JSON.stringify(result, null, 2);
3426
3224
  }
3427
3225
  }),
3428
- gitlab_get_todo_count: tool10({
3226
+ gitlab_get_todo_count: tool9({
3429
3227
  description: `Get the count of pending TODO items.
3430
3228
  Returns the total number of pending TODOs for the current user.`,
3431
3229
  args: {},
@@ -3438,15 +3236,15 @@ Returns the total number of pending TODOs for the current user.`,
3438
3236
  };
3439
3237
 
3440
3238
  // src/tools/wikis.ts
3441
- import { tool as tool11 } from "@opencode-ai/plugin";
3442
- var z11 = tool11.schema;
3239
+ import { tool as tool10 } from "@opencode-ai/plugin";
3240
+ var z10 = tool10.schema;
3443
3241
  var wikiTools = {
3444
- gitlab_get_wiki_page: tool11({
3242
+ gitlab_get_wiki_page: tool10({
3445
3243
  description: `Get a wiki page with its content.
3446
3244
  Returns the wiki page content and metadata.`,
3447
3245
  args: {
3448
- project_id: z11.string().describe("The project ID or URL-encoded path"),
3449
- slug: z11.string().describe("The slug (URL-friendly name) of the wiki page")
3246
+ project_id: z10.string().describe("The project ID or URL-encoded path"),
3247
+ slug: z10.string().describe("The slug (URL-friendly name) of the wiki page")
3450
3248
  },
3451
3249
  execute: async (args, _ctx) => {
3452
3250
  const client = getGitLabClient();
@@ -3457,15 +3255,15 @@ Returns the wiki page content and metadata.`,
3457
3255
  };
3458
3256
 
3459
3257
  // src/tools/work-items.ts
3460
- import { tool as tool12 } from "@opencode-ai/plugin";
3461
- var z12 = tool12.schema;
3258
+ import { tool as tool11 } from "@opencode-ai/plugin";
3259
+ var z11 = tool11.schema;
3462
3260
  var workItemTools = {
3463
- gitlab_get_work_item: tool12({
3261
+ gitlab_get_work_item: tool11({
3464
3262
  description: `Get a single work item (issue, epic, task, etc.).
3465
3263
  Work items are the new unified model for issues, epics, tasks, and other work tracking items in GitLab.`,
3466
3264
  args: {
3467
- project_id: z12.string().describe("The project ID or URL-encoded path"),
3468
- work_item_id: z12.number().describe("The ID of the work item")
3265
+ project_id: z11.string().describe("The project ID or URL-encoded path"),
3266
+ work_item_id: z11.number().describe("The ID of the work item")
3469
3267
  },
3470
3268
  execute: async (args, _ctx) => {
3471
3269
  const client = getGitLabClient();
@@ -3473,17 +3271,17 @@ Work items are the new unified model for issues, epics, tasks, and other work tr
3473
3271
  return JSON.stringify(workItem, null, 2);
3474
3272
  }
3475
3273
  }),
3476
- gitlab_list_work_items: tool12({
3274
+ gitlab_list_work_items: tool11({
3477
3275
  description: `List work items in a project or group.
3478
3276
  Work items include issues, epics, tasks, and other work tracking items.`,
3479
3277
  args: {
3480
- project_id: z12.string().optional().describe("The project ID or URL-encoded path"),
3481
- group_id: z12.string().optional().describe("The group ID or URL-encoded path"),
3482
- state: z12.enum(["opened", "closed", "all"]).optional().describe("Filter by state (default: opened)"),
3483
- search: z12.string().optional().describe("Search work items by title or description"),
3484
- labels: z12.string().optional().describe("Comma-separated list of labels to filter by"),
3485
- work_item_type: z12.string().optional().describe("Filter by work item type (e.g., 'Issue', 'Epic', 'Task')"),
3486
- limit: z12.number().optional().describe("Maximum number of results (default: 20)")
3278
+ project_id: z11.string().optional().describe("The project ID or URL-encoded path"),
3279
+ group_id: z11.string().optional().describe("The group ID or URL-encoded path"),
3280
+ state: z11.enum(["opened", "closed", "all"]).optional().describe("Filter by state (default: opened)"),
3281
+ search: z11.string().optional().describe("Search work items by title or description"),
3282
+ labels: z11.string().optional().describe("Comma-separated list of labels to filter by"),
3283
+ work_item_type: z11.string().optional().describe("Filter by work item type (e.g., 'Issue', 'Epic', 'Task')"),
3284
+ limit: z11.number().optional().describe("Maximum number of results (default: 20)")
3487
3285
  },
3488
3286
  execute: async (args, _ctx) => {
3489
3287
  const client = getGitLabClient();
@@ -3499,12 +3297,12 @@ Work items include issues, epics, tasks, and other work tracking items.`,
3499
3297
  return JSON.stringify(workItems, null, 2);
3500
3298
  }
3501
3299
  }),
3502
- gitlab_get_work_item_notes: tool12({
3300
+ gitlab_get_work_item_notes: tool11({
3503
3301
  description: `Get all comments for a work item.
3504
3302
  Returns all notes/comments on the work item in chronological order.`,
3505
3303
  args: {
3506
- project_id: z12.string().describe("The project ID or URL-encoded path"),
3507
- work_item_id: z12.number().describe("The ID of the work item")
3304
+ project_id: z11.string().describe("The project ID or URL-encoded path"),
3305
+ work_item_id: z11.number().describe("The ID of the work item")
3508
3306
  },
3509
3307
  execute: async (args, _ctx) => {
3510
3308
  const client = getGitLabClient();
@@ -3512,16 +3310,16 @@ Returns all notes/comments on the work item in chronological order.`,
3512
3310
  return JSON.stringify(notes, null, 2);
3513
3311
  }
3514
3312
  }),
3515
- gitlab_create_work_item: tool12({
3313
+ gitlab_create_work_item: tool11({
3516
3314
  description: `Create a new work item (issue, task, etc.).
3517
3315
  Work items are the new unified model for issues, epics, tasks, and other work tracking items.`,
3518
3316
  args: {
3519
- project_id: z12.string().describe("The project ID or URL-encoded path"),
3520
- title: z12.string().describe("The title of the work item"),
3521
- work_item_type_id: z12.number().describe("The ID of the work item type (e.g., 1 for Issue, 2 for Task)"),
3522
- description: z12.string().optional().describe("The description of the work item (supports Markdown)"),
3523
- labels: z12.array(z12.string()).optional().describe("Array of label names"),
3524
- assignee_ids: z12.array(z12.number()).optional().describe("Array of user IDs to assign")
3317
+ project_id: z11.string().describe("The project ID or URL-encoded path"),
3318
+ title: z11.string().describe("The title of the work item"),
3319
+ work_item_type_id: z11.number().describe("The ID of the work item type (e.g., 1 for Issue, 2 for Task)"),
3320
+ description: z11.string().optional().describe("The description of the work item (supports Markdown)"),
3321
+ labels: z11.array(z11.string()).optional().describe("Array of label names"),
3322
+ assignee_ids: z11.array(z11.number()).optional().describe("Array of user IDs to assign")
3525
3323
  },
3526
3324
  execute: async (args, _ctx) => {
3527
3325
  const client = getGitLabClient();
@@ -3535,17 +3333,17 @@ Work items are the new unified model for issues, epics, tasks, and other work tr
3535
3333
  return JSON.stringify(workItem, null, 2);
3536
3334
  }
3537
3335
  }),
3538
- gitlab_update_work_item: tool12({
3336
+ gitlab_update_work_item: tool11({
3539
3337
  description: `Update an existing work item.
3540
3338
  Can update title, description, state, labels, and assignees.`,
3541
3339
  args: {
3542
- project_id: z12.string().describe("The project ID or URL-encoded path"),
3543
- work_item_id: z12.number().describe("The ID of the work item"),
3544
- title: z12.string().optional().describe("The new title"),
3545
- description: z12.string().optional().describe("The new description (supports Markdown)"),
3546
- state_event: z12.enum(["close", "reopen"]).optional().describe("Change the state (close or reopen)"),
3547
- labels: z12.array(z12.string()).optional().describe("Array of label names"),
3548
- assignee_ids: z12.array(z12.number()).optional().describe("Array of user IDs to assign")
3340
+ project_id: z11.string().describe("The project ID or URL-encoded path"),
3341
+ work_item_id: z11.number().describe("The ID of the work item"),
3342
+ title: z11.string().optional().describe("The new title"),
3343
+ description: z11.string().optional().describe("The new description (supports Markdown)"),
3344
+ state_event: z11.enum(["close", "reopen"]).optional().describe("Change the state (close or reopen)"),
3345
+ labels: z11.array(z11.string()).optional().describe("Array of label names"),
3346
+ assignee_ids: z11.array(z11.number()).optional().describe("Array of user IDs to assign")
3549
3347
  },
3550
3348
  execute: async (args, _ctx) => {
3551
3349
  const client = getGitLabClient();
@@ -3559,12 +3357,12 @@ Can update title, description, state, labels, and assignees.`,
3559
3357
  return JSON.stringify(workItem, null, 2);
3560
3358
  }
3561
3359
  }),
3562
- gitlab_create_work_item_note: tool12({
3360
+ gitlab_create_work_item_note: tool11({
3563
3361
  description: `Create a comment on a work item.`,
3564
3362
  args: {
3565
- project_id: z12.string().describe("The project ID or URL-encoded path"),
3566
- work_item_id: z12.number().describe("The ID of the work item"),
3567
- body: z12.string().describe("The content of the note/comment (supports Markdown)")
3363
+ project_id: z11.string().describe("The project ID or URL-encoded path"),
3364
+ work_item_id: z11.number().describe("The ID of the work item"),
3365
+ body: z11.string().describe("The content of the note/comment (supports Markdown)")
3568
3366
  },
3569
3367
  execute: async (args, _ctx) => {
3570
3368
  const client = getGitLabClient();
@@ -3575,17 +3373,17 @@ Can update title, description, state, labels, and assignees.`,
3575
3373
  };
3576
3374
 
3577
3375
  // src/tools/discussions-unified.ts
3578
- import { tool as tool13 } from "@opencode-ai/plugin";
3579
- var z13 = tool13.schema;
3580
- var positionSchema = z13.object({
3581
- base_sha: z13.string().describe("SHA of the base commit"),
3582
- start_sha: z13.string().describe("SHA of the start commit"),
3583
- head_sha: z13.string().describe("SHA of the head commit"),
3584
- position_type: z13.enum(["text", "image"]).describe("Type of position"),
3585
- old_path: z13.string().optional().describe("Path of the file before changes"),
3586
- new_path: z13.string().optional().describe("Path of the file after changes"),
3587
- old_line: z13.number().optional().describe("Line number in the old version"),
3588
- new_line: z13.number().optional().describe("Line number in the new version")
3376
+ import { tool as tool12 } from "@opencode-ai/plugin";
3377
+ var z12 = tool12.schema;
3378
+ var positionSchema = z12.object({
3379
+ base_sha: z12.string().describe("SHA of the base commit"),
3380
+ start_sha: z12.string().describe("SHA of the start commit"),
3381
+ head_sha: z12.string().describe("SHA of the head commit"),
3382
+ position_type: z12.enum(["text", "image"]).describe("Type of position"),
3383
+ old_path: z12.string().optional().describe("Path of the file before changes"),
3384
+ new_path: z12.string().optional().describe("Path of the file after changes"),
3385
+ old_line: z12.number().optional().describe("Line number in the old version"),
3386
+ new_line: z12.number().optional().describe("Line number in the new version")
3589
3387
  });
3590
3388
  function validateResourceParams(resourceType, args) {
3591
3389
  switch (resourceType) {
@@ -3612,7 +3410,7 @@ var discussionsUnifiedTools = {
3612
3410
  /**
3613
3411
  * List discussions for any GitLab resource type
3614
3412
  */
3615
- gitlab_list_discussions: tool13({
3413
+ gitlab_list_discussions: tool12({
3616
3414
  description: `List discussions (comment threads) on any GitLab resource.
3617
3415
  Supports: merge_requests, issues, epics, commits, snippets.
3618
3416
 
@@ -3628,17 +3426,17 @@ Examples:
3628
3426
  - Commit: resource_type="commit", project_id="group/project", sha="abc123"
3629
3427
  - Snippet: resource_type="snippet", project_id="group/project", snippet_id=789`,
3630
3428
  args: {
3631
- resource_type: z13.enum(["merge_request", "issue", "epic", "commit", "snippet"]).describe("Type of GitLab resource"),
3632
- project_id: z13.string().optional().describe("Project ID or path. Required for merge_request, issue, commit, snippet"),
3633
- group_id: z13.string().optional().describe("Group ID or path. Required for epic"),
3634
- iid: z13.number().optional().describe("Internal ID of the resource (for merge_request, issue, epic)"),
3635
- sha: z13.string().optional().describe("Commit SHA (required for commit)"),
3636
- snippet_id: z13.number().optional().describe("Snippet ID (required for snippet)"),
3429
+ resource_type: z12.enum(["merge_request", "issue", "epic", "commit", "snippet"]).describe("Type of GitLab resource"),
3430
+ project_id: z12.string().optional().describe("Project ID or path. Required for merge_request, issue, commit, snippet"),
3431
+ group_id: z12.string().optional().describe("Group ID or path. Required for epic"),
3432
+ iid: z12.number().optional().describe("Internal ID of the resource (for merge_request, issue, epic)"),
3433
+ sha: z12.string().optional().describe("Commit SHA (required for commit)"),
3434
+ snippet_id: z12.number().optional().describe("Snippet ID (required for snippet)"),
3637
3435
  // Pagination
3638
- first: z13.number().optional().describe("Number of items to return (default: 20)"),
3639
- after: z13.string().optional().describe("Cursor for pagination - use endCursor from previous response"),
3640
- before: z13.string().optional().describe("Cursor for backward pagination"),
3641
- last: z13.number().optional().describe("Number of items from the end")
3436
+ first: z12.number().optional().describe("Number of items to return (default: 20)"),
3437
+ after: z12.string().optional().describe("Cursor for pagination - use endCursor from previous response"),
3438
+ before: z12.string().optional().describe("Cursor for backward pagination"),
3439
+ last: z12.number().optional().describe("Number of items from the end")
3642
3440
  },
3643
3441
  execute: async (args, _ctx) => {
3644
3442
  validateResourceParams(args.resource_type, args);
@@ -3692,7 +3490,7 @@ Examples:
3692
3490
  /**
3693
3491
  * Get a specific discussion thread from any GitLab resource
3694
3492
  */
3695
- gitlab_get_discussion: tool13({
3493
+ gitlab_get_discussion: tool12({
3696
3494
  description: `Get a specific discussion thread with all its replies.
3697
3495
  Returns the discussion with its 'notes' array containing all comments.
3698
3496
 
@@ -3705,13 +3503,13 @@ Required parameters vary by resource type:
3705
3503
  - commit: project_id, sha, discussion_id
3706
3504
  - snippet: project_id, snippet_id, discussion_id`,
3707
3505
  args: {
3708
- resource_type: z13.enum(["merge_request", "issue", "epic", "commit", "snippet"]).describe("Type of GitLab resource"),
3709
- discussion_id: z13.string().describe("The ID of the discussion thread"),
3710
- project_id: z13.string().optional().describe("Project ID or path"),
3711
- group_id: z13.string().optional().describe("Group ID or path (for epic)"),
3712
- iid: z13.number().optional().describe("Internal ID (for merge_request, issue, epic)"),
3713
- sha: z13.string().optional().describe("Commit SHA (for commit)"),
3714
- snippet_id: z13.number().optional().describe("Snippet ID (for snippet)")
3506
+ resource_type: z12.enum(["merge_request", "issue", "epic", "commit", "snippet"]).describe("Type of GitLab resource"),
3507
+ discussion_id: z12.string().describe("The ID of the discussion thread"),
3508
+ project_id: z12.string().optional().describe("Project ID or path"),
3509
+ group_id: z12.string().optional().describe("Group ID or path (for epic)"),
3510
+ iid: z12.number().optional().describe("Internal ID (for merge_request, issue, epic)"),
3511
+ sha: z12.string().optional().describe("Commit SHA (for commit)"),
3512
+ snippet_id: z12.number().optional().describe("Snippet ID (for snippet)")
3715
3513
  },
3716
3514
  execute: async (args, _ctx) => {
3717
3515
  validateResourceParams(args.resource_type, args);
@@ -3759,7 +3557,7 @@ Required parameters vary by resource type:
3759
3557
  /**
3760
3558
  * Create a new discussion thread or reply to an existing one
3761
3559
  */
3762
- gitlab_create_discussion: tool13({
3560
+ gitlab_create_discussion: tool12({
3763
3561
  description: `Create a new discussion thread or reply to an existing one.
3764
3562
 
3765
3563
  For NEW discussion: Omit discussion_id
@@ -3772,14 +3570,14 @@ Examples:
3772
3570
  - Reply to thread: resource_type="merge_request", ..., discussion_id="...", body="..."
3773
3571
  - Code comment: resource_type="merge_request", ..., body="...", position={base_sha, head_sha, ...}`,
3774
3572
  args: {
3775
- resource_type: z13.enum(["merge_request", "issue", "epic", "commit", "snippet"]).describe("Type of GitLab resource"),
3776
- body: z13.string().describe("The comment text (Markdown supported)"),
3777
- project_id: z13.string().optional().describe("Project ID or path"),
3778
- group_id: z13.string().optional().describe("Group ID or path (for epic)"),
3779
- iid: z13.number().optional().describe("Internal ID (for merge_request, issue, epic)"),
3780
- sha: z13.string().optional().describe("Commit SHA (for commit)"),
3781
- snippet_id: z13.number().optional().describe("Snippet ID (for snippet)"),
3782
- discussion_id: z13.string().optional().describe("If provided, replies to existing discussion. If omitted, creates new thread"),
3573
+ resource_type: z12.enum(["merge_request", "issue", "epic", "commit", "snippet"]).describe("Type of GitLab resource"),
3574
+ body: z12.string().describe("The comment text (Markdown supported)"),
3575
+ project_id: z12.string().optional().describe("Project ID or path"),
3576
+ group_id: z12.string().optional().describe("Group ID or path (for epic)"),
3577
+ iid: z12.number().optional().describe("Internal ID (for merge_request, issue, epic)"),
3578
+ sha: z12.string().optional().describe("Commit SHA (for commit)"),
3579
+ snippet_id: z12.number().optional().describe("Snippet ID (for snippet)"),
3580
+ discussion_id: z12.string().optional().describe("If provided, replies to existing discussion. If omitted, creates new thread"),
3783
3581
  position: positionSchema.optional().describe("Position for code-specific comments (MR/commit only)")
3784
3582
  },
3785
3583
  execute: async (args, _ctx) => {
@@ -3844,17 +3642,17 @@ Examples:
3844
3642
  /**
3845
3643
  * Resolve or unresolve a discussion thread
3846
3644
  */
3847
- gitlab_resolve_discussion: tool13({
3645
+ gitlab_resolve_discussion: tool12({
3848
3646
  description: `Mark a discussion thread as resolved or unresolve it.
3849
3647
  Only works for resolvable discussions (MRs and issues only).
3850
3648
 
3851
3649
  Use after addressing feedback to indicate the discussion is complete.`,
3852
3650
  args: {
3853
- resource_type: z13.enum(["merge_request", "issue"]).describe("Type of resource (only MR and issue discussions can be resolved)"),
3854
- action: z13.enum(["resolve", "unresolve"]).describe("Whether to resolve or unresolve"),
3855
- discussion_id: z13.string().describe("The ID of the discussion thread"),
3856
- project_id: z13.string().describe("Project ID or path"),
3857
- iid: z13.number().describe("Internal ID of the MR or issue")
3651
+ resource_type: z12.enum(["merge_request", "issue"]).describe("Type of resource (only MR and issue discussions can be resolved)"),
3652
+ action: z12.enum(["resolve", "unresolve"]).describe("Whether to resolve or unresolve"),
3653
+ discussion_id: z12.string().describe("The ID of the discussion thread"),
3654
+ project_id: z12.string().describe("Project ID or path"),
3655
+ iid: z12.number().describe("Internal ID of the MR or issue")
3858
3656
  },
3859
3657
  execute: async (args, _ctx) => {
3860
3658
  const client = getGitLabClient();
@@ -3893,6 +3691,224 @@ Use after addressing feedback to indicate the discussion is complete.`,
3893
3691
  })
3894
3692
  };
3895
3693
 
3694
+ // src/tools/notes-unified.ts
3695
+ import { tool as tool13 } from "@opencode-ai/plugin";
3696
+ var z13 = tool13.schema;
3697
+ var VALID_LIST_CREATE_TYPES = ["merge_request", "issue", "epic", "snippet"];
3698
+ var VALID_GET_NOTE_TYPES = ["issue", "epic"];
3699
+ function validationError2(param, resourceType) {
3700
+ return new Error(
3701
+ `Missing required parameter: '${param}' is required for resource_type '${resourceType}'`
3702
+ );
3703
+ }
3704
+ function validateListCreateParams(resourceType, args) {
3705
+ if (!VALID_LIST_CREATE_TYPES.includes(resourceType)) {
3706
+ throw new Error(
3707
+ `Invalid resource_type '${resourceType}'. Must be one of: ${VALID_LIST_CREATE_TYPES.join(", ")}`
3708
+ );
3709
+ }
3710
+ switch (resourceType) {
3711
+ case "merge_request":
3712
+ case "issue":
3713
+ if (!args.project_id) throw validationError2("project_id", resourceType);
3714
+ if (args.iid == null) throw validationError2("iid", resourceType);
3715
+ break;
3716
+ case "epic":
3717
+ if (!args.group_id) throw validationError2("group_id", resourceType);
3718
+ if (args.iid == null) throw validationError2("iid", resourceType);
3719
+ break;
3720
+ case "snippet":
3721
+ if (!args.project_id) throw validationError2("project_id", resourceType);
3722
+ if (args.snippet_id == null) throw validationError2("snippet_id", resourceType);
3723
+ break;
3724
+ }
3725
+ }
3726
+ function validateGetNoteParams(resourceType, args) {
3727
+ if (!VALID_GET_NOTE_TYPES.includes(resourceType)) {
3728
+ throw new Error(
3729
+ `Invalid resource_type '${resourceType}'. Must be one of: ${VALID_GET_NOTE_TYPES.join(", ")}`
3730
+ );
3731
+ }
3732
+ if (args.note_id == null) throw validationError2("note_id", resourceType);
3733
+ switch (resourceType) {
3734
+ case "issue":
3735
+ if (!args.project_id) throw validationError2("project_id", resourceType);
3736
+ if (args.iid == null) throw validationError2("iid", resourceType);
3737
+ break;
3738
+ case "epic":
3739
+ if (!args.group_id) throw validationError2("group_id", resourceType);
3740
+ if (args.iid == null) throw validationError2("iid", resourceType);
3741
+ break;
3742
+ }
3743
+ }
3744
+ var notesUnifiedTools = {
3745
+ /**
3746
+ * List notes/comments for any GitLab resource type
3747
+ */
3748
+ gitlab_list_notes: tool13({
3749
+ description: `List all notes/comments on any GitLab resource using GraphQL API with pagination support.
3750
+ Returns all comments including system notes in chronological order.
3751
+ This is easier to read than discussions which have nested structure.
3752
+
3753
+ The response includes pagination information (pageInfo) with cursors for fetching additional pages.
3754
+ Use 'after' with the 'endCursor' from pageInfo to get the next page.
3755
+ Use 'before' with the 'startCursor' from pageInfo to get the previous page.
3756
+
3757
+ Examples:
3758
+ - MR: resource_type="merge_request", project_id="group/project", iid=123
3759
+ - Issue: resource_type="issue", project_id="group/project", iid=456
3760
+ - Epic: resource_type="epic", group_id="my-group", iid=1
3761
+ - Snippet: resource_type="snippet", project_id="group/project", snippet_id=789`,
3762
+ args: {
3763
+ resource_type: z13.enum(["merge_request", "issue", "epic", "snippet"]).describe("Type of GitLab resource"),
3764
+ project_id: z13.string().optional().describe("Project ID or path. Required for merge_request, issue, snippet"),
3765
+ group_id: z13.string().optional().describe("Group ID or path. Required for epic"),
3766
+ iid: z13.number().optional().describe("Internal ID of the resource (for merge_request, issue, epic)"),
3767
+ snippet_id: z13.number().optional().describe("Snippet ID (required for snippet)"),
3768
+ // Pagination
3769
+ first: z13.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
3770
+ after: z13.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
3771
+ last: z13.number().optional().describe("Number of items to return from the end (for backward pagination)"),
3772
+ before: z13.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
3773
+ },
3774
+ execute: async (args, _ctx) => {
3775
+ validateListCreateParams(args.resource_type, args);
3776
+ const client = getGitLabClient();
3777
+ const paginationOptions = {
3778
+ first: args.first,
3779
+ after: args.after,
3780
+ last: args.last,
3781
+ before: args.before
3782
+ };
3783
+ switch (args.resource_type) {
3784
+ case "merge_request":
3785
+ return JSON.stringify(
3786
+ await client.listMrNotes(args.project_id, args.iid, paginationOptions),
3787
+ null,
3788
+ 2
3789
+ );
3790
+ case "issue":
3791
+ return JSON.stringify(
3792
+ await client.listIssueNotes(args.project_id, args.iid, paginationOptions),
3793
+ null,
3794
+ 2
3795
+ );
3796
+ case "epic":
3797
+ return JSON.stringify(
3798
+ await client.listEpicNotes(args.group_id, args.iid, paginationOptions),
3799
+ null,
3800
+ 2
3801
+ );
3802
+ case "snippet":
3803
+ return JSON.stringify(
3804
+ await client.listSnippetNotes(args.project_id, args.snippet_id, paginationOptions),
3805
+ null,
3806
+ 2
3807
+ );
3808
+ default:
3809
+ throw new Error(`Unsupported resource type: ${args.resource_type}`);
3810
+ }
3811
+ }
3812
+ }),
3813
+ /**
3814
+ * Get a single note/comment by its ID
3815
+ */
3816
+ gitlab_get_note: tool13({
3817
+ description: `Get a single note/comment from an issue or epic by its ID.
3818
+ Returns the full details of a specific note including author, body, timestamps, and metadata.
3819
+ Useful when you need to retrieve a specific comment without fetching all notes.
3820
+
3821
+ Supports: issues, epics (MR notes use discussions API)
3822
+
3823
+ Examples:
3824
+ - Issue note: resource_type="issue", project_id="group/project", iid=456, note_id=123
3825
+ - Epic note: resource_type="epic", group_id="my-group", iid=1, note_id=456`,
3826
+ args: {
3827
+ resource_type: z13.enum(["issue", "epic"]).describe("Type of GitLab resource (issue or epic only)"),
3828
+ note_id: z13.number().describe("The ID of the note to retrieve"),
3829
+ project_id: z13.string().optional().describe("Project ID or path. Required for issue"),
3830
+ group_id: z13.string().optional().describe("Group ID or path. Required for epic"),
3831
+ iid: z13.number().describe("Internal ID of the issue or epic")
3832
+ },
3833
+ execute: async (args, _ctx) => {
3834
+ validateGetNoteParams(args.resource_type, args);
3835
+ const client = getGitLabClient();
3836
+ switch (args.resource_type) {
3837
+ case "issue":
3838
+ return JSON.stringify(
3839
+ await client.getIssueNote(args.project_id, args.iid, args.note_id),
3840
+ null,
3841
+ 2
3842
+ );
3843
+ case "epic":
3844
+ return JSON.stringify(
3845
+ await client.getEpicNote(args.group_id, args.iid, args.note_id),
3846
+ null,
3847
+ 2
3848
+ );
3849
+ default:
3850
+ throw new Error(`Unsupported resource type: ${args.resource_type}`);
3851
+ }
3852
+ }
3853
+ }),
3854
+ /**
3855
+ * Create a simple note/comment on any GitLab resource
3856
+ */
3857
+ gitlab_create_note: tool13({
3858
+ description: `Add a simple comment/note to any GitLab resource.
3859
+ Creates a standalone comment (not part of a thread).
3860
+
3861
+ For replying to existing discussion threads, use gitlab_create_discussion
3862
+ with discussion_id parameter instead.
3863
+
3864
+ Examples:
3865
+ - MR comment: resource_type="merge_request", project_id="group/project", iid=123, body="LGTM!"
3866
+ - Issue comment: resource_type="issue", project_id="group/project", iid=456, body="Working on this"
3867
+ - Epic comment: resource_type="epic", group_id="my-group", iid=1, body="Planning complete"
3868
+ - Snippet comment: resource_type="snippet", project_id="group/project", snippet_id=789, body="Nice code!"`,
3869
+ args: {
3870
+ resource_type: z13.enum(["merge_request", "issue", "epic", "snippet"]).describe("Type of GitLab resource"),
3871
+ body: z13.string().describe("The content of the note/comment (supports Markdown)"),
3872
+ project_id: z13.string().optional().describe("Project ID or path. Required for merge_request, issue, snippet"),
3873
+ group_id: z13.string().optional().describe("Group ID or path. Required for epic"),
3874
+ iid: z13.number().optional().describe("Internal ID of the resource (for merge_request, issue, epic)"),
3875
+ snippet_id: z13.number().optional().describe("Snippet ID (required for snippet)")
3876
+ },
3877
+ execute: async (args, _ctx) => {
3878
+ validateListCreateParams(args.resource_type, args);
3879
+ const client = getGitLabClient();
3880
+ switch (args.resource_type) {
3881
+ case "merge_request":
3882
+ return JSON.stringify(
3883
+ await client.createMrNote(args.project_id, args.iid, args.body),
3884
+ null,
3885
+ 2
3886
+ );
3887
+ case "issue":
3888
+ return JSON.stringify(
3889
+ await client.createIssueNote(args.project_id, args.iid, args.body),
3890
+ null,
3891
+ 2
3892
+ );
3893
+ case "epic":
3894
+ return JSON.stringify(
3895
+ await client.createEpicNote(args.group_id, args.iid, args.body),
3896
+ null,
3897
+ 2
3898
+ );
3899
+ case "snippet":
3900
+ return JSON.stringify(
3901
+ await client.createSnippetNote(args.project_id, args.snippet_id, args.body),
3902
+ null,
3903
+ 2
3904
+ );
3905
+ default:
3906
+ throw new Error(`Unsupported resource type: ${args.resource_type}`);
3907
+ }
3908
+ }
3909
+ })
3910
+ };
3911
+
3896
3912
  // src/tools/git.ts
3897
3913
  import { tool as tool14 } from "@opencode-ai/plugin";
3898
3914
 
@@ -4249,8 +4265,6 @@ var gitlabPlugin = async (_input) => {
4249
4265
  ...userTools,
4250
4266
  // Security Tools
4251
4267
  ...securityTools,
4252
- // Snippet Tools
4253
- ...snippetTools,
4254
4268
  // TODO Tools
4255
4269
  ...todoTools,
4256
4270
  // Wiki Tools
@@ -4259,6 +4273,8 @@ var gitlabPlugin = async (_input) => {
4259
4273
  ...workItemTools,
4260
4274
  // Unified Discussion Tools (covers MR, issue, epic, commit, snippet discussions)
4261
4275
  ...discussionsUnifiedTools,
4276
+ // Unified Notes Tools (covers MR, issue, epic, snippet notes)
4277
+ ...notesUnifiedTools,
4262
4278
  // Git Tools
4263
4279
  ...gitTools,
4264
4280
  // Audit Tools