@gitlab/opencode-gitlab-plugin 1.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/README.md ADDED
@@ -0,0 +1,1283 @@
1
+ # OpenCode GitLab Plugin
2
+
3
+ [![GitLab CI](https://gitlab.com/vglafirov/opencode-gitlab-plugin/badges/main/pipeline.svg)](https://gitlab.com/vglafirov/opencode-gitlab-plugin/-/pipelines)
4
+ [![npm version](https://img.shields.io/npm/v/@vglafirov/opencode-gitlab-plugin.svg)](https://gitlab.com/vglafirov/opencode-gitlab-plugin/-/packages)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ A comprehensive GitLab API plugin for OpenCode that provides AI-powered access to GitLab's REST API. This plugin enables seamless interaction with merge requests, issues, pipelines, repositories, epics, and more through natural language commands.
8
+
9
+ ## 📋 Table of Contents
10
+
11
+ - [Features](#-features)
12
+ - [Architecture](#-architecture)
13
+ - [Installation](#-installation)
14
+ - [Configuration](#-configuration)
15
+ - [Available Tools](#-available-tools)
16
+ - [Usage Examples](#-usage-examples)
17
+ - [Development](#-development)
18
+ - [CI/CD Pipeline](#-cicd-pipeline)
19
+ - [API Reference](#-api-reference)
20
+ - [Contributing](#-contributing)
21
+ - [License](#-license)
22
+
23
+ ## ✨ Features
24
+
25
+ ### Core Capabilities
26
+
27
+ - **🔀 Merge Requests**: Full CRUD operations, discussions, notes, changes, commits, and pipelines
28
+ - **📝 Issues**: Create, read, update, and comment on issues with advanced filtering
29
+ - **🎯 Work Items**: Unified interface for issues, epics, tasks, and other work tracking items
30
+ - **🚀 CI/CD Pipelines**: Monitor, analyze, and retry pipeline jobs with detailed logs
31
+ - **📦 Repository Operations**: File management, commits, branches, and tree navigation
32
+ - **🔍 Advanced Search**: Multi-scope search across projects, code, issues, and merge requests
33
+ - **📊 Epics**: Enterprise-level epic management with issue associations
34
+ - **✅ TODOs**: Personal task management and notifications
35
+ - **🔒 Security**: Vulnerability scanning and security report access
36
+ - **📚 Wiki**: Wiki page content retrieval
37
+ - **👥 Project Management**: Member management and project details
38
+
39
+ ### Technical Features
40
+
41
+ - **TypeScript**: Full type safety with comprehensive type definitions
42
+ - **ESM Support**: Modern ES modules for optimal tree-shaking
43
+ - **Zod Validation**: Runtime schema validation for all API inputs
44
+ - **GraphQL Support**: Native GraphQL API support with type-safe mutations and queries
45
+ - **GID Validation**: Automatic validation of GitLab Global IDs with descriptive error messages
46
+ - **Error Handling**: Robust error handling with detailed error messages
47
+ - **Authentication**: Multiple authentication methods (OAuth, API tokens)
48
+ - **Rate Limiting**: Built-in handling for GitLab API rate limits
49
+ - **Caching**: Efficient API response handling
50
+ - **Modular Architecture**: Clean separation of concerns with client and tool modules
51
+ - **Comprehensive Testing**: 142 tests with full coverage of all features
52
+
53
+ ## 🏗️ Architecture
54
+
55
+ ### System Architecture
56
+
57
+ ```mermaid
58
+ graph TB
59
+ subgraph "OpenCode Environment"
60
+ AI[AI Assistant]
61
+ Plugin[GitLab Plugin]
62
+ end
63
+
64
+ subgraph "Plugin Components"
65
+ Tools[Tool Definitions]
66
+ Client[GitLab API Client]
67
+ Auth[Authentication Manager]
68
+ Validator[Zod Schema Validator]
69
+ end
70
+
71
+ subgraph "GitLab API"
72
+ REST[REST API v4]
73
+ MR[Merge Requests]
74
+ Issues[Issues]
75
+ Pipelines[Pipelines]
76
+ Repos[Repositories]
77
+ Epics[Epics]
78
+ Security[Security]
79
+ end
80
+
81
+ AI -->|Natural Language| Plugin
82
+ Plugin --> Tools
83
+ Tools --> Validator
84
+ Validator --> Client
85
+ Client --> Auth
86
+ Auth --> REST
87
+ REST --> MR
88
+ REST --> Issues
89
+ REST --> Pipelines
90
+ REST --> Repos
91
+ REST --> Epics
92
+ REST --> Security
93
+ ```
94
+
95
+ ### Plugin Structure
96
+
97
+ ```mermaid
98
+ graph LR
99
+ subgraph "src/index.ts"
100
+ A[GitLabApiClient Class]
101
+ B[Authentication Functions]
102
+ C[Tool Definitions]
103
+ D[Plugin Export]
104
+ end
105
+
106
+ A --> A1[HTTP Methods]
107
+ A --> A2[Merge Request APIs]
108
+ A --> A3[Issue APIs]
109
+ A --> A4[Pipeline APIs]
110
+ A --> A5[Repository APIs]
111
+ A --> A6[Epic APIs]
112
+ A --> A7[Search APIs]
113
+
114
+ B --> B1[readTokenFromAuthStorage]
115
+ B --> B2[getGitLabClient]
116
+
117
+ C --> C1[60+ Tool Definitions]
118
+
119
+ D --> A
120
+ D --> B
121
+ D --> C
122
+ ```
123
+
124
+ ### Authentication Flow
125
+
126
+ ```mermaid
127
+ sequenceDiagram
128
+ participant User
129
+ participant Plugin
130
+ participant AuthManager
131
+ participant Storage
132
+ participant GitLab
133
+
134
+ User->>Plugin: Initialize Plugin
135
+ Plugin->>AuthManager: getGitLabClient()
136
+ AuthManager->>AuthManager: Check GITLAB_TOKEN env
137
+ alt Token in Environment
138
+ AuthManager->>GitLab: Use env token
139
+ else No env token
140
+ AuthManager->>Storage: Read ~/.local/share/opencode/auth.json
141
+ Storage->>AuthManager: Return token
142
+ AuthManager->>GitLab: Use stored token
143
+ end
144
+ GitLab->>Plugin: API Response
145
+ Plugin->>User: Tool Result
146
+ ```
147
+
148
+ ## 📦 Installation
149
+
150
+ ### Prerequisites
151
+
152
+ - Node.js >= 18.0.0
153
+ - npm >= 9.0.0 or Bun
154
+ - GitLab account with API access
155
+ - GitLab Personal Access Token or OAuth token
156
+
157
+ ### Install from GitLab Package Registry
158
+
159
+ ```bash
160
+ # Configure npm to use GitLab Package Registry
161
+ npm config set @gitlab-org:registry https://gitlab.com/api/v4/projects/76386853/packages/npm/
162
+
163
+ # Install the package
164
+ npm install @vglafirov/opencode-gitlab-plugin
165
+
166
+ # Or with Bun
167
+ bun add @vglafirov/opencode-gitlab-plugin
168
+
169
+ # Or with yarn
170
+ yarn add @vglafirov/opencode-gitlab-plugin
171
+ ```
172
+
173
+ ### Install from GitLab Repository (Development)
174
+
175
+ ```bash
176
+ # Using npm
177
+ npm install git+https://gitlab.com/vglafirov/opencode-gitlab-plugin.git
178
+
179
+ # Using Bun
180
+ bun add git+https://gitlab.com/vglafirov/opencode-gitlab-plugin.git
181
+ ```
182
+
183
+ ### Install Specific Version
184
+
185
+ ```bash
186
+ # Install specific version from registry
187
+ npm install @vglafirov/opencode-gitlab-plugin@1.0.0
188
+
189
+ # Install from specific git tag
190
+ npm install git+https://gitlab.com/vglafirov/opencode-gitlab-plugin.git#v1.0.0
191
+
192
+ # Install from specific branch
193
+ npm install git+https://gitlab.com/vglafirov/opencode-gitlab-plugin.git#main
194
+ ```
195
+
196
+ ### Using package.json
197
+
198
+ Add to your `package.json`:
199
+
200
+ ```json
201
+ {
202
+ "dependencies": {
203
+ "@vglafirov/opencode-gitlab-plugin": "^1.0.0"
204
+ },
205
+ "publishConfig": {
206
+ "@gitlab-org:registry": "https://gitlab.com/api/v4/projects/76386853/packages/npm/"
207
+ }
208
+ }
209
+ ```
210
+
211
+ Then run:
212
+
213
+ ```bash
214
+ npm install
215
+ ```
216
+
217
+ ## ⚙️ Configuration
218
+
219
+ ### Environment Variables
220
+
221
+ ```bash
222
+ # Required: GitLab API Token
223
+ export GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx
224
+
225
+ # Optional: Custom GitLab Instance (defaults to https://gitlab.com)
226
+ export GITLAB_INSTANCE_URL=https://gitlab.example.com
227
+ ```
228
+
229
+ ### OpenCode Configuration
230
+
231
+ Add the following plugin to your opencode configuration `~/.config/opencode/opencode.json`:
232
+
233
+ ```json
234
+ {
235
+ "$schema": "https://opencode.ai/config.json",
236
+ "plugin": ["@vglafirov/opencode-gitlab-plugin"]
237
+ }
238
+ ```
239
+
240
+ ### Authentication Storage
241
+
242
+ The plugin supports reading tokens from OpenCode's auth storage:
243
+
244
+ **Location**: `~/.local/share/opencode/auth.json`
245
+
246
+ **Format**:
247
+
248
+ ```json
249
+ {
250
+ "gitlab": {
251
+ "type": "oauth",
252
+ "access": "your-oauth-token"
253
+ }
254
+ }
255
+ ```
256
+
257
+ Or for API tokens:
258
+
259
+ ```json
260
+ {
261
+ "gitlab": {
262
+ "type": "api",
263
+ "key": "glpat-xxxxxxxxxxxxxxxxxxxx"
264
+ }
265
+ }
266
+ ```
267
+
268
+ ### Token Priority
269
+
270
+ 1. `GITLAB_TOKEN` environment variable (highest priority)
271
+ 2. OpenCode auth storage (`~/.local/share/opencode/auth.json`)
272
+ 3. Error if no token found
273
+
274
+ ## 🛠️ Available Tools
275
+
276
+ The plugin provides **68+ tools** organized into the following categories:
277
+
278
+ ### Merge Request Tools (13 tools)
279
+
280
+ ```mermaid
281
+ graph LR
282
+ MR[Merge Requests] --> Get[gitlab_get_merge_request]
283
+ MR --> List[gitlab_list_merge_requests]
284
+ MR --> Create[gitlab_create_merge_request]
285
+ MR --> Update[gitlab_update_merge_request]
286
+ MR --> Changes[gitlab_get_mr_changes]
287
+ MR --> Discussions[gitlab_list_mr_discussions]
288
+ MR --> Notes[gitlab_list_mr_notes]
289
+ MR --> CreateNote[gitlab_create_mr_note]
290
+ MR --> Commits[gitlab_get_mr_commits]
291
+ MR --> Pipelines[gitlab_get_mr_pipelines]
292
+ ```
293
+
294
+ | Tool | Description |
295
+ | ----------------------------- | ------------------------------------------ |
296
+ | `gitlab_get_merge_request` | Get details of a specific merge request |
297
+ | `gitlab_list_merge_requests` | List merge requests with filtering |
298
+ | `gitlab_create_merge_request` | Create a new merge request |
299
+ | `gitlab_update_merge_request` | Update an existing merge request |
300
+ | `gitlab_get_mr_changes` | Get file changes/diff for a merge request |
301
+ | `gitlab_list_mr_discussions` | List discussion threads on a merge request |
302
+ | `gitlab_list_mr_notes` | List all comments in flat structure |
303
+ | `gitlab_create_mr_note` | Add a comment to a merge request |
304
+ | `gitlab_get_mr_commits` | Get commits in a merge request |
305
+ | `gitlab_get_mr_pipelines` | Get pipelines for a merge request |
306
+
307
+ ### Issue Tools (5 tools)
308
+
309
+ | Tool | Description |
310
+ | -------------------------- | ------------------------------- |
311
+ | `gitlab_create_issue` | Create a new issue in a project |
312
+ | `gitlab_get_issue` | Get details of a specific issue |
313
+ | `gitlab_list_issues` | List issues with filtering |
314
+ | `gitlab_list_issue_notes` | List all comments on an issue |
315
+ | `gitlab_create_issue_note` | Add a comment to an issue |
316
+
317
+ ### Work Item Tools (7 tools)
318
+
319
+ | Tool | Description |
320
+ | ------------------------------ | ------------------------------------------ |
321
+ | `gitlab_get_work_item` | Get a single work item (issue, epic, task) |
322
+ | `gitlab_list_work_items` | List work items in a project or group |
323
+ | `gitlab_get_work_item_notes` | Get all comments for a work item |
324
+ | `gitlab_create_work_item` | Create a new work item |
325
+ | `gitlab_update_work_item` | Update an existing work item |
326
+ | `gitlab_create_work_item_note` | Add a comment to a work item |
327
+
328
+ ### Pipeline Tools (6 tools)
329
+
330
+ | Tool | Description |
331
+ | ---------------------------------- | ---------------------------------- |
332
+ | `gitlab_list_pipelines` | List pipelines for a project |
333
+ | `gitlab_get_pipeline` | Get details of a specific pipeline |
334
+ | `gitlab_list_pipeline_jobs` | List jobs for a pipeline |
335
+ | `gitlab_get_job_log` | Get log output of a CI job |
336
+ | `gitlab_retry_job` | Retry a failed or canceled job |
337
+ | `gitlab_get_pipeline_failing_jobs` | Get all failed jobs in a pipeline |
338
+
339
+ ### Repository Tools (9 tools)
340
+
341
+ | Tool | Description |
342
+ | ----------------------------- | ------------------------------------------ |
343
+ | `gitlab_get_file` | Get contents of a file from repository |
344
+ | `gitlab_get_commit` | Get a single commit with full details |
345
+ | `gitlab_list_commits` | List commits with filtering |
346
+ | `gitlab_get_commit_diff` | Get diff for a specific commit |
347
+ | `gitlab_create_commit` | Create a commit with multiple file actions |
348
+ | `gitlab_list_repository_tree` | List files and directories |
349
+ | `gitlab_list_branches` | List branches in a repository |
350
+
351
+ ### Search Tools (4 tools)
352
+
353
+ | Tool | Description |
354
+ | ----------------------------- | -------------------------------------------------------- |
355
+ | `gitlab_search` | Search across GitLab (projects, issues, MRs, code, etc.) |
356
+ | `gitlab_issue_search` | Specialized issue search |
357
+ | `gitlab_blob_search` | Search file content in repositories |
358
+ | `gitlab_merge_request_search` | Specialized merge request search |
359
+
360
+ ### Epic Tools (9 tools)
361
+
362
+ | Tool | Description |
363
+ | ------------------------------- | -------------------------------------- |
364
+ | `gitlab_get_epic` | Get details of a specific epic |
365
+ | `gitlab_list_epics` | List epics for a group |
366
+ | `gitlab_create_epic` | Create a new epic |
367
+ | `gitlab_update_epic` | Update an existing epic |
368
+ | `gitlab_list_epic_issues` | Get all issues associated with an epic |
369
+ | `gitlab_add_issue_to_epic` | Link an issue to an epic |
370
+ | `gitlab_remove_issue_from_epic` | Unlink an issue from an epic |
371
+ | `gitlab_list_epic_notes` | List all comments on an epic |
372
+ | `gitlab_create_epic_note` | Add a comment to an epic |
373
+
374
+ ### TODO Tools (4 tools)
375
+
376
+ | Tool | Description |
377
+ | ---------------------------- | -------------------------------- |
378
+ | `gitlab_list_todos` | List TODO items for current user |
379
+ | `gitlab_mark_todo_done` | Mark a specific TODO as done |
380
+ | `gitlab_mark_all_todos_done` | Mark all TODOs as done |
381
+ | `gitlab_get_todo_count` | Get count of pending TODOs |
382
+
383
+ ### Project & User Tools (3 tools)
384
+
385
+ | Tool | Description |
386
+ | ----------------------------- | --------------------------------- |
387
+ | `gitlab_get_project` | Get details of a specific project |
388
+ | `gitlab_list_project_members` | List members of a project |
389
+ | `gitlab_get_current_user` | Get current user information |
390
+
391
+ ### Security Tools (8 tools)
392
+
393
+ | Tool | Description |
394
+ | ----------------------------------------- | ------------------------------------------------ |
395
+ | `gitlab_list_vulnerabilities` | List security vulnerabilities |
396
+ | `gitlab_get_vulnerability_details` | Get details for a specific vulnerability |
397
+ | `gitlab_create_vulnerability_issue` | Create issue linked to vulnerabilities (GraphQL) |
398
+ | `gitlab_dismiss_vulnerability` | Dismiss vulnerability with reason (GraphQL) |
399
+ | `gitlab_confirm_vulnerability` | Confirm a security vulnerability (GraphQL) |
400
+ | `gitlab_revert_vulnerability_to_detected` | Revert vulnerability to detected state (GraphQL) |
401
+ | `gitlab_update_vulnerability_severity` | Update vulnerability severity level (GraphQL) |
402
+ | `gitlab_link_vulnerability_to_issue` | Link existing issue to vulnerabilities (GraphQL) |
403
+
404
+ **Note**: All GraphQL-based security tools include automatic GID (Global ID) format validation to ensure correct parameter formats before making API calls.
405
+
406
+ ### Wiki Tools (1 tool)
407
+
408
+ | Tool | Description |
409
+ | ---------------------- | -------------------------------- |
410
+ | `gitlab_get_wiki_page` | Get a wiki page with its content |
411
+
412
+ ## 💡 Usage Examples
413
+
414
+ ### Example 1: Create and Manage Issues
415
+
416
+ ```javascript
417
+ import gitlabPlugin from '@vglafirov/opencode-gitlab-plugin';
418
+
419
+ const plugin = await gitlabPlugin({});
420
+
421
+ // Create a new issue
422
+ const issue = await plugin.tool.gitlab_create_issue.execute({
423
+ project_id: 'my-group/my-project',
424
+ title: 'Fix authentication bug',
425
+ description:
426
+ '## Problem\n\nUsers cannot login with OAuth.\n\n## Steps to Reproduce\n1. Go to login page\n2. Click OAuth button\n3. Error occurs',
427
+ labels: 'bug,authentication,priority::high',
428
+ assignee_ids: [42],
429
+ milestone_id: 10,
430
+ due_date: '2025-12-31',
431
+ });
432
+
433
+ console.log(`Issue created: ${issue.web_url}`);
434
+
435
+ // Add a comment to the issue
436
+ await plugin.tool.gitlab_create_issue_note.execute({
437
+ project_id: 'my-group/my-project',
438
+ issue_iid: issue.iid,
439
+ body: 'I will start working on this today.',
440
+ });
441
+
442
+ // List all issues with specific labels
443
+ const issues = await plugin.tool.gitlab_list_issues.execute({
444
+ project_id: 'my-group/my-project',
445
+ labels: 'bug',
446
+ state: 'opened',
447
+ });
448
+ ```
449
+
450
+ ### Example 2: Review Merge Request
451
+
452
+ ```javascript
453
+ // Get merge request details
454
+ const mr = await plugin.tool.gitlab_get_merge_request.execute({
455
+ project_id: 'gitlab-org/gitlab',
456
+ mr_iid: 12345,
457
+ include_changes: true,
458
+ });
459
+
460
+ // Get discussions
461
+ const discussions = await plugin.tool.gitlab_list_mr_discussions.execute({
462
+ project_id: 'gitlab-org/gitlab',
463
+ mr_iid: 12345,
464
+ });
465
+
466
+ // Add a review comment
467
+ await plugin.tool.gitlab_create_mr_note.execute({
468
+ project_id: 'gitlab-org/gitlab',
469
+ mr_iid: 12345,
470
+ body: 'LGTM! Great work on this feature.',
471
+ });
472
+ ```
473
+
474
+ ### Example 3: Debug Failed Pipeline
475
+
476
+ ```javascript
477
+ // List recent pipelines
478
+ const pipelines = await plugin.tool.gitlab_list_pipelines.execute({
479
+ project_id: 'my-group/my-project',
480
+ status: 'failed',
481
+ limit: 5,
482
+ });
483
+
484
+ // Get failed jobs
485
+ const failedJobs = await plugin.tool.gitlab_get_pipeline_failing_jobs.execute({
486
+ project_id: 'my-group/my-project',
487
+ pipeline_id: pipelines[0].id,
488
+ });
489
+
490
+ // Get job logs
491
+ for (const job of failedJobs) {
492
+ const log = await plugin.tool.gitlab_get_job_log.execute({
493
+ project_id: 'my-group/my-project',
494
+ job_id: job.id,
495
+ });
496
+ console.log(`Job ${job.name} failed with:\n${log}`);
497
+ }
498
+
499
+ // Retry failed jobs
500
+ await plugin.tool.gitlab_retry_job.execute({
501
+ project_id: 'my-group/my-project',
502
+ job_id: failedJobs[0].id,
503
+ });
504
+ ```
505
+
506
+ ### Example 4: Create and Manage Epic
507
+
508
+ ```javascript
509
+ // Create an epic
510
+ const epic = await plugin.tool.gitlab_create_epic.execute({
511
+ group_id: 'my-group',
512
+ title: 'Q1 2025 Features',
513
+ description: 'All features planned for Q1 2025',
514
+ start_date: '2025-01-01',
515
+ end_date: '2025-03-31',
516
+ labels: 'Q1,planning',
517
+ });
518
+
519
+ // Add issues to epic
520
+ await plugin.tool.gitlab_add_issue_to_epic.execute({
521
+ group_id: 'my-group',
522
+ epic_iid: epic.iid,
523
+ issue_id: 123,
524
+ });
525
+
526
+ // List all issues in epic
527
+ const epicIssues = await plugin.tool.gitlab_list_epic_issues.execute({
528
+ group_id: 'my-group',
529
+ epic_iid: epic.iid,
530
+ });
531
+
532
+ // Add a comment
533
+ await plugin.tool.gitlab_create_epic_note.execute({
534
+ group_id: 'my-group',
535
+ epic_iid: epic.iid,
536
+ body: 'Epic created and issues linked successfully!',
537
+ });
538
+ ```
539
+
540
+ ### Example 5: Search and Analyze Code
541
+
542
+ ```javascript
543
+ // Search for code containing specific patterns
544
+ const codeResults = await plugin.tool.gitlab_blob_search.execute({
545
+ search: 'async function processPayment',
546
+ project_id: 'my-group/my-project',
547
+ limit: 10,
548
+ });
549
+
550
+ // Search for related issues
551
+ const issues = await plugin.tool.gitlab_issue_search.execute({
552
+ search: 'payment processing bug',
553
+ project_id: 'my-group/my-project',
554
+ state: 'opened',
555
+ });
556
+
557
+ // Get file content
558
+ const fileContent = await plugin.tool.gitlab_get_file.execute({
559
+ project_id: 'my-group/my-project',
560
+ file_path: 'src/payment/processor.ts',
561
+ ref: 'main',
562
+ });
563
+ ```
564
+
565
+ ### Example 6: Manage TODOs
566
+
567
+ ```javascript
568
+ // Get TODO count
569
+ const todoCount = await plugin.tool.gitlab_get_todo_count.execute({});
570
+
571
+ // List pending TODOs
572
+ const todos = await plugin.tool.gitlab_list_todos.execute({
573
+ state: 'pending',
574
+ type: 'MergeRequest',
575
+ limit: 20,
576
+ });
577
+
578
+ // Mark specific TODO as done
579
+ await plugin.tool.gitlab_mark_todo_done.execute({
580
+ todo_id: todos[0].id,
581
+ });
582
+
583
+ // Mark all TODOs as done
584
+ await plugin.tool.gitlab_mark_all_todos_done.execute({});
585
+ ```
586
+
587
+ ### Example 7: Create Commit with Multiple Files
588
+
589
+ ```javascript
590
+ // Create a commit with multiple file operations
591
+ const commit = await plugin.tool.gitlab_create_commit.execute({
592
+ project_id: 'my-group/my-project',
593
+ branch: 'feature/new-api',
594
+ commit_message: 'feat: add new API endpoints',
595
+ actions: [
596
+ {
597
+ action: 'create',
598
+ file_path: 'src/api/v2/users.ts',
599
+ content: 'export const getUsers = async () => { ... }',
600
+ },
601
+ {
602
+ action: 'update',
603
+ file_path: 'src/api/index.ts',
604
+ content: 'export * from "./v2/users";',
605
+ },
606
+ {
607
+ action: 'delete',
608
+ file_path: 'src/api/deprecated.ts',
609
+ },
610
+ ],
611
+ author_name: 'John Doe',
612
+ author_email: 'john@example.com',
613
+ });
614
+ ```
615
+
616
+ ### Example 8: Manage Security Vulnerabilities
617
+
618
+ ```javascript
619
+ // List vulnerabilities in a project
620
+ const vulnerabilities = await plugin.tool.gitlab_list_vulnerabilities.execute({
621
+ project_id: 'my-group/my-project',
622
+ state: 'detected',
623
+ severity: 'high',
624
+ report_type: 'sast',
625
+ });
626
+
627
+ // Create an issue for critical vulnerabilities
628
+ const issue = await plugin.tool.gitlab_create_vulnerability_issue.execute({
629
+ project_path: 'my-group/my-project',
630
+ vulnerability_ids: ['gid://gitlab/Vulnerability/123', 'gid://gitlab/Vulnerability/124'],
631
+ });
632
+
633
+ // Dismiss a false positive
634
+ await plugin.tool.gitlab_dismiss_vulnerability.execute({
635
+ vulnerability_id: 'gid://gitlab/Vulnerability/125',
636
+ reason: 'FALSE_POSITIVE',
637
+ comment: 'This is a test file and not part of production code',
638
+ });
639
+
640
+ // Confirm a real vulnerability
641
+ await plugin.tool.gitlab_confirm_vulnerability.execute({
642
+ vulnerability_id: 'gid://gitlab/Vulnerability/126',
643
+ comment: 'Confirmed - needs immediate attention',
644
+ });
645
+
646
+ // Update severity based on assessment
647
+ await plugin.tool.gitlab_update_vulnerability_severity.execute({
648
+ vulnerability_ids: ['gid://gitlab/Vulnerability/127'],
649
+ severity: 'CRITICAL',
650
+ comment: 'Upgrading to critical - affects production authentication',
651
+ });
652
+
653
+ // Link vulnerabilities to existing issue
654
+ await plugin.tool.gitlab_link_vulnerability_to_issue.execute({
655
+ issue_id: 'gid://gitlab/Issue/42',
656
+ vulnerability_ids: ['gid://gitlab/Vulnerability/128', 'gid://gitlab/Vulnerability/129'],
657
+ });
658
+ ```
659
+
660
+ ## 🔧 Development
661
+
662
+ ### Project Structure
663
+
664
+ ```
665
+ opencode-gitlab-plugin/
666
+ ├── src/
667
+ │ ├── client/ # API client modules
668
+ │ │ ├── base.ts # Base client with HTTP & GraphQL methods
669
+ │ │ ├── security.ts # Security/vulnerability management
670
+ │ │ ├── issues.ts # Issue management
671
+ │ │ ├── merge-requests.ts # Merge request operations
672
+ │ │ ├── pipelines.ts # CI/CD pipeline operations
673
+ │ │ ├── repository.ts # Repository operations
674
+ │ │ ├── epics.ts # Epic management
675
+ │ │ ├── search.ts # Search operations
676
+ │ │ ├── todos.ts # TODO management
677
+ │ │ ├── wikis.ts # Wiki operations
678
+ │ │ ├── work-items.ts # Work item operations
679
+ │ │ ├── audit.ts # Audit events
680
+ │ │ ├── git.ts # Git operations
681
+ │ │ └── index.ts # Client exports
682
+ │ ├── tools/ # Tool definitions
683
+ │ │ ├── security.ts # Security tool definitions
684
+ │ │ ├── issues.ts # Issue tool definitions
685
+ │ │ ├── merge-requests.ts # MR tool definitions
686
+ │ │ ├── pipelines.ts # Pipeline tool definitions
687
+ │ │ ├── repository.ts # Repository tool definitions
688
+ │ │ └── ... # Other tool definitions
689
+ │ ├── index.ts # Main plugin entry point
690
+ │ ├── utils.ts # Utility functions
691
+ │ └── validation.ts # GID validation utilities
692
+ ├── tests/ # Test suite (142 tests)
693
+ │ ├── client/ # Client tests
694
+ │ ├── tools/ # Tool tests
695
+ │ ├── validation.test.ts # Validation tests
696
+ │ └── utils.test.ts # Utility tests
697
+ ├── dist/ # Compiled output (generated)
698
+ │ ├── index.js # ESM bundle
699
+ │ └── index.d.ts # TypeScript definitions
700
+ ├── .husky/ # Git hooks
701
+ │ ├── commit-msg # Commitlint hook
702
+ │ └── pre-commit # Lint-staged hook
703
+ ├── .gitlab-ci.yml # CI/CD pipeline configuration
704
+ ├── package.json # Package metadata
705
+ ├── tsconfig.json # TypeScript configuration
706
+ ├── vitest.config.ts # Vitest test configuration
707
+ ├── .eslintrc.json # ESLint configuration
708
+ ├── .prettierrc.json # Prettier configuration
709
+ ├── .commitlintrc.json # Commitlint configuration
710
+ ├── .releaserc.json # Semantic-release configuration
711
+ ├── CHANGELOG.md # Auto-generated changelog
712
+ ├── INSTALLATION.md # Installation guide
713
+ └── README.md # This file
714
+ ```
715
+
716
+ ### Setup Development Environment
717
+
718
+ ```bash
719
+ # Clone the repository
720
+ git clone https://gitlab.com/vglafirov/opencode-gitlab-plugin.git
721
+ cd opencode-gitlab-plugin
722
+
723
+ # Install dependencies
724
+ npm install
725
+
726
+ # Build the plugin
727
+ npm run build
728
+
729
+ # Watch mode for development
730
+ npm run dev
731
+
732
+ # Run linting
733
+ npm run lint
734
+
735
+ # Fix linting issues
736
+ npm run lint:fix
737
+
738
+ # Format code
739
+ npm run format
740
+
741
+ # Check formatting
742
+ npm run format:check
743
+ ```
744
+
745
+ ### Git Hooks
746
+
747
+ The project uses Husky for Git hooks:
748
+
749
+ - **pre-commit**: Runs lint-staged to lint and format staged files
750
+ - **commit-msg**: Validates commit messages using commitlint
751
+
752
+ ### Commit Message Convention
753
+
754
+ This project follows [Conventional Commits](https://www.conventionalcommits.org/):
755
+
756
+ ```
757
+ <type>(<scope>): <subject>
758
+
759
+ <body>
760
+
761
+ <footer>
762
+ ```
763
+
764
+ **Types:**
765
+
766
+ - `feat`: New feature
767
+ - `fix`: Bug fix
768
+ - `docs`: Documentation changes
769
+ - `style`: Code style changes (formatting, etc.)
770
+ - `refactor`: Code refactoring
771
+ - `perf`: Performance improvements
772
+ - `test`: Adding or updating tests
773
+ - `build`: Build system changes
774
+ - `ci`: CI/CD changes
775
+ - `chore`: Other changes (dependencies, etc.)
776
+ - `revert`: Revert a previous commit
777
+
778
+ **Examples:**
779
+
780
+ ```bash
781
+ git commit -m "feat: add support for GitLab wiki pages"
782
+ git commit -m "fix: handle empty API responses correctly"
783
+ git commit -m "docs: update installation instructions"
784
+ git commit -m "ci: add automated release workflow"
785
+ ```
786
+
787
+ ### Building
788
+
789
+ ```bash
790
+ # Build for production
791
+ npm run build
792
+
793
+ # The build process:
794
+ # 1. Compiles TypeScript to ESM
795
+ # 2. Generates type definitions
796
+ # 3. Outputs to dist/ directory
797
+ ```
798
+
799
+ ### Testing
800
+
801
+ ```bash
802
+ # Run tests
803
+ npm test
804
+
805
+ # Run tests in watch mode
806
+ npm run test:watch
807
+
808
+ # Run tests with coverage
809
+ npm run test:coverage
810
+ ```
811
+
812
+ **Test Coverage:**
813
+
814
+ - **142 tests** across 18 test files
815
+ - Client tests for all API methods
816
+ - Tool tests for all tool definitions
817
+ - Validation tests for GID utilities
818
+ - GraphQL method tests
819
+ - All tests passing ✅
820
+
821
+ ## 🚀 CI/CD Pipeline
822
+
823
+ ### Pipeline Stages
824
+
825
+ ```mermaid
826
+ graph LR
827
+ A[Test] --> B[Build]
828
+ B --> C[Release]
829
+
830
+ A --> A1[Lint]
831
+ A --> A2[Format Check]
832
+ A --> A3[Unit Tests]
833
+
834
+ B --> B1[TypeScript Build]
835
+ B --> B2[Generate Types]
836
+
837
+ C --> C1[Semantic Release]
838
+ C --> C2[Publish to Registry]
839
+ C --> C3[Create Git Tag]
840
+ C --> C4[Update Changelog]
841
+ ```
842
+
843
+ ### Pipeline Configuration
844
+
845
+ The `.gitlab-ci.yml` defines the following stages:
846
+
847
+ #### 1. Test Stage
848
+
849
+ - **test:lint**: Runs ESLint on source code
850
+ - **test:format**: Checks code formatting with Prettier
851
+ - **test:unit**: Runs unit tests (placeholder)
852
+
853
+ #### 2. Build Stage
854
+
855
+ - **build**: Compiles TypeScript and generates artifacts
856
+ - Uses tsup for bundling
857
+ - Generates ESM output
858
+ - Creates TypeScript definitions
859
+ - Artifacts expire in 1 week
860
+
861
+ #### 3. Release Stage
862
+
863
+ - **release**: Automated versioning and publishing
864
+ - Only runs on main branch
865
+ - Uses semantic-release
866
+ - Publishes to GitLab Package Registry
867
+ - Creates Git tags
868
+ - Updates CHANGELOG.md
869
+ - Artifacts never expire
870
+
871
+ ### Semantic Release Workflow
872
+
873
+ ```mermaid
874
+ sequenceDiagram
875
+ participant Dev as Developer
876
+ participant Git as Git Repository
877
+ participant CI as GitLab CI
878
+ participant SR as Semantic Release
879
+ participant Reg as Package Registry
880
+
881
+ Dev->>Git: Push to main branch
882
+ Git->>CI: Trigger pipeline
883
+ CI->>CI: Run tests
884
+ CI->>CI: Build package
885
+ CI->>SR: Run semantic-release
886
+ SR->>SR: Analyze commits
887
+ SR->>SR: Determine version
888
+ SR->>SR: Generate changelog
889
+ SR->>Git: Create tag & commit
890
+ SR->>Reg: Publish package
891
+ SR->>Git: Create GitLab release
892
+ ```
893
+
894
+ ### Release Process
895
+
896
+ The release process is fully automated using semantic-release:
897
+
898
+ 1. **Commit Analysis**: Analyzes commit messages since last release
899
+ 2. **Version Calculation**: Determines next version based on commit types
900
+ - `fix:` → Patch version (1.0.x)
901
+ - `feat:` → Minor version (1.x.0)
902
+ - `BREAKING CHANGE:` → Major version (x.0.0)
903
+ 3. **Changelog Generation**: Updates CHANGELOG.md
904
+ 4. **Package Publishing**: Publishes to GitLab Package Registry
905
+ 5. **Git Tagging**: Creates and pushes version tag
906
+ 6. **GitLab Release**: Creates release notes on GitLab
907
+
908
+ ### Environment Variables
909
+
910
+ The CI/CD pipeline uses the following variables:
911
+
912
+ - `CI_JOB_TOKEN`: GitLab CI token (automatic)
913
+ - `CI_PROJECT_ID`: Project ID (automatic)
914
+ - `CI_API_V4_URL`: GitLab API URL (automatic)
915
+ - `HUSKY`: Set to `0` to disable hooks in CI
916
+
917
+ ## 📚 API Reference
918
+
919
+ ### GitLabApiClient Class
920
+
921
+ The core API client that handles all GitLab REST API interactions.
922
+
923
+ #### Constructor
924
+
925
+ ```typescript
926
+ constructor(instanceUrl: string, token: string)
927
+ ```
928
+
929
+ #### HTTP Methods
930
+
931
+ ```typescript
932
+ async fetch<T>(method: string, path: string, body?: unknown): Promise<T>
933
+ async fetchText(method: string, path: string): Promise<string>
934
+ ```
935
+
936
+ #### GraphQL Methods
937
+
938
+ ```typescript
939
+ /**
940
+ * Execute a GraphQL query or mutation
941
+ * @template T - The expected type of the data field in the GraphQL response
942
+ * @param query - The GraphQL query or mutation string
943
+ * @param variables - Optional variables for the query
944
+ * @returns The data from the GraphQL response
945
+ */
946
+ async fetchGraphQL<T>(query: string, variables?: Record<string, unknown>): Promise<T>
947
+ ```
948
+
949
+ **Features:**
950
+
951
+ - Full TypeScript type safety with generic return types
952
+ - Automatic error handling for both HTTP and GraphQL errors
953
+ - Support for query variables
954
+ - Used by all GraphQL-based security tools
955
+
956
+ **Example:**
957
+
958
+ ```typescript
959
+ const result = await client.fetchGraphQL<{ vulnerability: { id: string } }>(
960
+ `mutation($id: VulnerabilityID!) {
961
+ vulnerabilityConfirm(input: { id: $id }) {
962
+ vulnerability { id state }
963
+ errors
964
+ }
965
+ }`,
966
+ { id: 'gid://gitlab/Vulnerability/123' }
967
+ );
968
+ ```
969
+
970
+ #### Project ID Encoding
971
+
972
+ ```typescript
973
+ private encodeProjectId(projectId: string): string
974
+ ```
975
+
976
+ Handles URL encoding for project paths (e.g., `gitlab-org/gitlab` → `gitlab-org%2Fgitlab`)
977
+
978
+ ### Authentication Functions
979
+
980
+ #### readTokenFromAuthStorage
981
+
982
+ ```typescript
983
+ function readTokenFromAuthStorage(): string | undefined;
984
+ ```
985
+
986
+ Reads GitLab token from OpenCode auth storage (`~/.local/share/opencode/auth.json`).
987
+
988
+ Supports:
989
+
990
+ - OAuth tokens: `{ type: "oauth", access: "token" }`
991
+ - API tokens: `{ type: "api", key: "token" }`
992
+
993
+ #### getGitLabClient
994
+
995
+ ```typescript
996
+ function getGitLabClient(): GitLabApiClient;
997
+ ```
998
+
999
+ Creates and returns a configured GitLab API client.
1000
+
1001
+ Priority:
1002
+
1003
+ 1. `GITLAB_TOKEN` environment variable
1004
+ 2. OpenCode auth storage
1005
+ 3. Throws error if no token found
1006
+
1007
+ ### Validation Functions
1008
+
1009
+ #### isValidGid
1010
+
1011
+ ```typescript
1012
+ function isValidGid(gid: string, expectedType?: string): boolean;
1013
+ ```
1014
+
1015
+ Validates GitLab Global ID (GID) format.
1016
+
1017
+ **Parameters:**
1018
+
1019
+ - `gid` - The GID to validate (e.g., `gid://gitlab/Vulnerability/123`)
1020
+ - `expectedType` - Optional expected resource type (e.g., `'Vulnerability'`, `'Issue'`)
1021
+
1022
+ **Returns:** `true` if valid, `false` otherwise
1023
+
1024
+ **Example:**
1025
+
1026
+ ```typescript
1027
+ isValidGid('gid://gitlab/Vulnerability/123'); // true
1028
+ isValidGid('gid://gitlab/Issue/456', 'Issue'); // true
1029
+ isValidGid('gid://gitlab/Vulnerability/123', 'Issue'); // false (wrong type)
1030
+ isValidGid('invalid-gid'); // false
1031
+ ```
1032
+
1033
+ #### validateGid
1034
+
1035
+ ```typescript
1036
+ function validateGid(gid: string, expectedType?: string): void;
1037
+ ```
1038
+
1039
+ Validates GID format and throws descriptive error if invalid.
1040
+
1041
+ **Parameters:**
1042
+
1043
+ - `gid` - The GID to validate
1044
+ - `expectedType` - Optional expected resource type
1045
+
1046
+ **Throws:** `Error` with descriptive message if GID format is invalid
1047
+
1048
+ **Example:**
1049
+
1050
+ ```typescript
1051
+ validateGid('gid://gitlab/Vulnerability/123'); // No error
1052
+ validateGid('invalid-gid'); // Throws: "Invalid GitLab Global ID: 'invalid-gid'. Expected format: gid://gitlab/ResourceType/{id}"
1053
+ validateGid('gid://gitlab/Issue/123', 'Vulnerability'); // Throws: "Invalid GitLab Global ID of type 'Vulnerability'..."
1054
+ ```
1055
+
1056
+ **Usage in Security Tools:**
1057
+
1058
+ All GraphQL-based security tools automatically validate GID parameters:
1059
+
1060
+ - `createVulnerabilityIssue()` - validates vulnerability IDs
1061
+ - `dismissVulnerability()` - validates vulnerability ID
1062
+ - `confirmVulnerability()` - validates vulnerability ID
1063
+ - `revertVulnerability()` - validates vulnerability ID
1064
+ - `updateVulnerabilitySeverity()` - validates vulnerability IDs
1065
+ - `linkVulnerabilityToIssue()` - validates both issue ID and vulnerability IDs
1066
+
1067
+ ### Tool Schema Validation
1068
+
1069
+ All tools use Zod for runtime validation:
1070
+
1071
+ ```typescript
1072
+ import { tool } from '@opencode-ai/plugin';
1073
+ const z = tool.schema; // Zod v4 compatible
1074
+
1075
+ // Example tool definition
1076
+ gitlab_get_merge_request: tool({
1077
+ description: 'Get details of a specific merge request',
1078
+ args: {
1079
+ project_id: z.string().describe('The project ID or URL-encoded path'),
1080
+ mr_iid: z.number().describe('The internal ID of the merge request'),
1081
+ include_changes: z.boolean().optional().describe('Include file changes'),
1082
+ },
1083
+ execute: async (args, ctx) => {
1084
+ // Implementation
1085
+ },
1086
+ });
1087
+ ```
1088
+
1089
+ ### Error Handling
1090
+
1091
+ All API calls include comprehensive error handling:
1092
+
1093
+ ```typescript
1094
+ if (!response.ok) {
1095
+ const errorText = await response.text();
1096
+ throw new Error(`GitLab API error ${response.status}: ${errorText}`);
1097
+ }
1098
+ ```
1099
+
1100
+ ### Response Formatting
1101
+
1102
+ All tool responses are JSON-formatted:
1103
+
1104
+ ```typescript
1105
+ return JSON.stringify(result, null, 2);
1106
+ ```
1107
+
1108
+ ## 🔐 Security Considerations
1109
+
1110
+ ### Token Storage
1111
+
1112
+ - **Environment Variables**: Recommended for CI/CD and production
1113
+ - **Auth Storage**: Convenient for local development
1114
+ - **Never commit tokens**: Use `.gitignore` for sensitive files
1115
+
1116
+ ### API Permissions
1117
+
1118
+ The plugin requires a GitLab token with appropriate scopes:
1119
+
1120
+ - `api`: Full API access (recommended)
1121
+ - `read_api`: Read-only access (limited functionality)
1122
+ - `read_repository`: Repository read access
1123
+ - `write_repository`: Repository write access (for commits)
1124
+
1125
+ ### Rate Limiting
1126
+
1127
+ GitLab API has rate limits:
1128
+
1129
+ - **Authenticated requests**: 2,000 requests per minute
1130
+ - **Unauthenticated requests**: 10 requests per minute
1131
+
1132
+ The plugin does not implement rate limiting logic. Consider implementing retry logic in your application.
1133
+
1134
+ ### HTTPS Only
1135
+
1136
+ The plugin enforces HTTPS for all API calls. HTTP URLs are not supported.
1137
+
1138
+ ## 🤝 Contributing
1139
+
1140
+ Contributions are welcome! Please follow these guidelines:
1141
+
1142
+ ### Contribution Workflow
1143
+
1144
+ ```mermaid
1145
+ graph TD
1146
+ A[Fork Repository] --> B[Create Feature Branch]
1147
+ B --> C[Make Changes]
1148
+ C --> D[Write Tests]
1149
+ D --> E[Run Linting]
1150
+ E --> F[Commit with Convention]
1151
+ F --> G[Push to Fork]
1152
+ G --> H[Create Merge Request]
1153
+ H --> I[Code Review]
1154
+ I --> J{Approved?}
1155
+ J -->|Yes| K[Merge to Main]
1156
+ J -->|No| C
1157
+ K --> L[Automated Release]
1158
+ ```
1159
+
1160
+ ### Steps to Contribute
1161
+
1162
+ 1. **Fork the repository**
1163
+
1164
+ ```bash
1165
+ # Fork on GitLab UI, then clone
1166
+ git clone https://gitlab.com/YOUR_USERNAME/opencode-gitlab-plugin.git
1167
+ ```
1168
+
1169
+ 2. **Create a feature branch**
1170
+
1171
+ ```bash
1172
+ git checkout -b feat/my-new-feature
1173
+ ```
1174
+
1175
+ 3. **Make your changes**
1176
+ - Follow TypeScript best practices
1177
+ - Add JSDoc comments for public APIs
1178
+ - Update documentation if needed
1179
+
1180
+ 4. **Run quality checks**
1181
+
1182
+ ```bash
1183
+ npm run lint
1184
+ npm run format
1185
+ npm run build
1186
+ npm test
1187
+ ```
1188
+
1189
+ 5. **Commit with conventional commits**
1190
+
1191
+ ```bash
1192
+ git commit -m "feat: add new tool for project milestones"
1193
+ ```
1194
+
1195
+ 6. **Push and create MR**
1196
+
1197
+ ```bash
1198
+ git push origin feat/my-new-feature
1199
+ # Create merge request on GitLab
1200
+ ```
1201
+
1202
+ ### Code Style
1203
+
1204
+ - **TypeScript**: Strict mode enabled
1205
+ - **ESLint**: Follow configured rules
1206
+ - **Prettier**: Auto-format on commit
1207
+ - **Line Length**: 100 characters max
1208
+ - **Indentation**: 2 spaces, no tabs
1209
+
1210
+ ### Adding New Tools
1211
+
1212
+ To
1213
+
1214
+ ---
1215
+
1216
+ ## Assistant
1217
+
1218
+ add a new GitLab API tool:
1219
+
1220
+ 1. **Add API method to GitLabApiClient**
1221
+
1222
+ ```typescript
1223
+ async getProjectMilestones(projectId: string) {
1224
+ const encodedProject = this.encodeProjectId(projectId);
1225
+ return this.fetch<Record<string, unknown>[]>(
1226
+ 'GET',
1227
+ `/projects/${encodedProject}/milestones`
1228
+ );
1229
+ }
1230
+ ```
1231
+
1232
+ 2. **Add tool definition**
1233
+
1234
+ ```typescript
1235
+ gitlab_list_milestones: tool({
1236
+ description: 'List milestones for a project',
1237
+ args: {
1238
+ project_id: z.string().describe('The project ID or path'),
1239
+ state: z.enum(['active', 'closed', 'all']).optional(),
1240
+ },
1241
+ execute: async (args, _ctx) => {
1242
+ const client = getGitLabClient();
1243
+ const milestones = await client.getProjectMilestones(args.project_id);
1244
+ return JSON.stringify(milestones, null, 2);
1245
+ },
1246
+ });
1247
+ ```
1248
+
1249
+ 3. **Update documentation**
1250
+ - Add tool to README.md
1251
+ - Update tool count
1252
+ - Add usage example if applicable
1253
+
1254
+ ## 🔗 Links
1255
+
1256
+ - **Repository**: <https://gitlab.com/vglafirov/opencode-gitlab-plugin>
1257
+ - **Package Registry**: <https://gitlab.com/vglafirov/opencode-gitlab-plugin/-/packages>
1258
+ - **Issues**: <https://gitlab.com/vglafirov/opencode-gitlab-plugin/-/issues>
1259
+ - **Merge Requests**: <https://gitlab.com/vglafirov/opencode-gitlab-plugin/-/merge_requests>
1260
+ - **CI/CD Pipelines**: <https://gitlab.com/vglafirov/opencode-gitlab-plugin/-/pipelines>
1261
+ - **GitLab API Documentation**: <https://docs.gitlab.com/ee/api/>
1262
+ - **OpenCode Plugin SDK**: <https://github.com/opencode-ai/plugin>
1263
+
1264
+ ## 🙏 Acknowledgments
1265
+
1266
+ - **OpenCode Team**: For the plugin SDK and framework
1267
+ - **GitLab**: For the comprehensive REST API
1268
+ - **Contributors**: All contributors to this project
1269
+
1270
+ ## 📞 Support
1271
+
1272
+ For questions, issues, or feature requests:
1273
+
1274
+ 1. **Check existing issues**: <https://gitlab.com/vglafirov/opencode-gitlab-plugin/-/issues>
1275
+ 2. **Create new issue**: Use issue templates for bugs or features
1276
+ 3. **Discussions**: Use GitLab discussions for questions
1277
+ 4. **Email**: Contact the maintainer via GitLab profile
1278
+
1279
+ ---
1280
+
1281
+ **Made with ❤️ for the OpenCode community**
1282
+
1283
+ ---