opencode-gitlab-dap 1.6.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -1
- package/dist/index.cjs +266 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +266 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -110,7 +110,9 @@ The plugin provides a multi-round design workflow:
|
|
|
110
110
|
|
|
111
111
|
The vendored `flow_v2.json` schema from GitLab Rails powers client-side validation using `ajv`, catching errors before hitting the API.
|
|
112
112
|
|
|
113
|
-
###
|
|
113
|
+
### 26 Tools
|
|
114
|
+
|
|
115
|
+
#### DAP Tools (20)
|
|
114
116
|
|
|
115
117
|
| Tool | Description |
|
|
116
118
|
| --------------------------------- | ------------------------------------------------------- |
|
|
@@ -135,6 +137,34 @@ The vendored `flow_v2.json` schema from GitLab Rails powers client-side validati
|
|
|
135
137
|
| `gitlab_get_workflow_status` | Monitor workflow execution status and logs |
|
|
136
138
|
| `gitlab_list_project_mcp_servers` | List MCP servers available for project agents |
|
|
137
139
|
|
|
140
|
+
#### Wiki Memory Tools (6)
|
|
141
|
+
|
|
142
|
+
Persistent project memory and skill storage via GitLab wikis.
|
|
143
|
+
|
|
144
|
+
| Tool | Description |
|
|
145
|
+
| -------------------- | -------------------------------------------------------- |
|
|
146
|
+
| `gitlab_wiki_read` | Read a wiki page by slug |
|
|
147
|
+
| `gitlab_wiki_write` | Create or update a wiki page (upsert) |
|
|
148
|
+
| `gitlab_wiki_append` | Append text to a wiki page (create if missing) |
|
|
149
|
+
| `gitlab_wiki_list` | List wiki pages with optional prefix filter |
|
|
150
|
+
| `gitlab_wiki_delete` | Delete a wiki page |
|
|
151
|
+
| `gitlab_wiki_search` | Search wiki pages by content (server-side GitLab search) |
|
|
152
|
+
|
|
153
|
+
All wiki tools support both project wikis (default) and group wikis (`scope="groups"`).
|
|
154
|
+
|
|
155
|
+
##### Recommended Wiki Structure
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
memory/
|
|
159
|
+
├── facts # Stable project truths (append-only)
|
|
160
|
+
├── decisions # Architecture decisions with context
|
|
161
|
+
├── patterns # Observations across sessions
|
|
162
|
+
└── sessions/ # Per-session learning logs
|
|
163
|
+
skills/
|
|
164
|
+
├── index # Skill routing table
|
|
165
|
+
└── <name> # Individual skill pages
|
|
166
|
+
```
|
|
167
|
+
|
|
138
168
|
### Dynamic Refresh
|
|
139
169
|
|
|
140
170
|
After enabling or disabling an agent/flow, the plugin automatically refreshes the
|
package/dist/index.cjs
CHANGED
|
@@ -2254,6 +2254,8 @@ prompts:
|
|
|
2254
2254
|
user: "Fix this vulnerability: {{vuln_data}}"
|
|
2255
2255
|
placeholder: history
|
|
2256
2256
|
\`\`\``;
|
|
2257
|
+
var WIKI_MEMORY_HINT = `## Wiki Memory
|
|
2258
|
+
Persistent project memory and skills are available via GitLab wiki tools (gitlab_wiki_list, gitlab_wiki_read, gitlab_wiki_write, gitlab_wiki_append, gitlab_wiki_search, gitlab_wiki_delete). Use gitlab_wiki_list to discover available memory pages and project or group specific skills.`;
|
|
2257
2259
|
|
|
2258
2260
|
// src/hooks.ts
|
|
2259
2261
|
function buildFlowSubagentPrompt(flow, projectPath, projectUrl) {
|
|
@@ -2413,6 +2415,7 @@ function makeSystemTransformHook(flowAgents, getAuthCache) {
|
|
|
2413
2415
|
}
|
|
2414
2416
|
if (getAuthCache()) {
|
|
2415
2417
|
output.system.push(AGENT_CREATION_GUIDELINES);
|
|
2418
|
+
output.system.push(WIKI_MEMORY_HINT);
|
|
2416
2419
|
}
|
|
2417
2420
|
};
|
|
2418
2421
|
}
|
|
@@ -3521,6 +3524,267 @@ function makeMcpTools(getCachedAgents) {
|
|
|
3521
3524
|
};
|
|
3522
3525
|
}
|
|
3523
3526
|
|
|
3527
|
+
// src/tools/wiki-tools.ts
|
|
3528
|
+
var import_plugin5 = require("@opencode-ai/plugin");
|
|
3529
|
+
|
|
3530
|
+
// src/wiki.ts
|
|
3531
|
+
function wikiApi(instanceUrl, token, scope, id, path = "") {
|
|
3532
|
+
const base = instanceUrl.replace(/\/$/, "");
|
|
3533
|
+
const encodedId = encodeURIComponent(String(id));
|
|
3534
|
+
return {
|
|
3535
|
+
url: `${base}/api/v4/${scope}/${encodedId}/wikis${path}`,
|
|
3536
|
+
headers: {
|
|
3537
|
+
Authorization: `Bearer ${token}`,
|
|
3538
|
+
"Content-Type": "application/json"
|
|
3539
|
+
}
|
|
3540
|
+
};
|
|
3541
|
+
}
|
|
3542
|
+
async function handleResponse(res) {
|
|
3543
|
+
if (res.ok) return res.json();
|
|
3544
|
+
const status = res.status;
|
|
3545
|
+
if (status === 404)
|
|
3546
|
+
throw new Error(
|
|
3547
|
+
"Page not found. Use gitlab_wiki_list to see available pages, or gitlab_wiki_write to create it."
|
|
3548
|
+
);
|
|
3549
|
+
if (status === 403)
|
|
3550
|
+
throw new Error(
|
|
3551
|
+
"Permission denied. The wiki may not be enabled for this project. Enable it in Settings > General > Visibility."
|
|
3552
|
+
);
|
|
3553
|
+
if (status === 422)
|
|
3554
|
+
throw new Error("Invalid page title or content. Check that the slug and content are valid.");
|
|
3555
|
+
if (status === 401) throw new Error("Authentication failed. Check your GitLab token.");
|
|
3556
|
+
const text = await res.text();
|
|
3557
|
+
throw new Error(`Wiki API error (${status}): ${text}`);
|
|
3558
|
+
}
|
|
3559
|
+
async function listWikiPages(instanceUrl, token, scope, id, withContent) {
|
|
3560
|
+
const { url, headers } = wikiApi(instanceUrl, token, scope, id);
|
|
3561
|
+
const fullUrl = withContent ? `${url}?with_content=1` : url;
|
|
3562
|
+
const res = await fetch(fullUrl, { headers });
|
|
3563
|
+
return handleResponse(res);
|
|
3564
|
+
}
|
|
3565
|
+
async function getWikiPage(instanceUrl, token, scope, id, slug) {
|
|
3566
|
+
const encodedSlug = encodeURIComponent(slug);
|
|
3567
|
+
const { url, headers } = wikiApi(instanceUrl, token, scope, id, `/${encodedSlug}`);
|
|
3568
|
+
const res = await fetch(url, { headers });
|
|
3569
|
+
return handleResponse(res);
|
|
3570
|
+
}
|
|
3571
|
+
async function createWikiPage(instanceUrl, token, scope, id, title, content, format = "markdown") {
|
|
3572
|
+
const { url, headers } = wikiApi(instanceUrl, token, scope, id);
|
|
3573
|
+
const res = await fetch(url, {
|
|
3574
|
+
method: "POST",
|
|
3575
|
+
headers,
|
|
3576
|
+
body: JSON.stringify({ title, content, format })
|
|
3577
|
+
});
|
|
3578
|
+
return handleResponse(res);
|
|
3579
|
+
}
|
|
3580
|
+
async function updateWikiPage(instanceUrl, token, scope, id, slug, content, title) {
|
|
3581
|
+
const encodedSlug = encodeURIComponent(slug);
|
|
3582
|
+
const { url, headers } = wikiApi(instanceUrl, token, scope, id, `/${encodedSlug}`);
|
|
3583
|
+
const body = { content };
|
|
3584
|
+
if (title) body.title = title;
|
|
3585
|
+
const res = await fetch(url, {
|
|
3586
|
+
method: "PUT",
|
|
3587
|
+
headers,
|
|
3588
|
+
body: JSON.stringify(body)
|
|
3589
|
+
});
|
|
3590
|
+
return handleResponse(res);
|
|
3591
|
+
}
|
|
3592
|
+
async function deleteWikiPage(instanceUrl, token, scope, id, slug) {
|
|
3593
|
+
const encodedSlug = encodeURIComponent(slug);
|
|
3594
|
+
const { url, headers } = wikiApi(instanceUrl, token, scope, id, `/${encodedSlug}`);
|
|
3595
|
+
const res = await fetch(url, { method: "DELETE", headers });
|
|
3596
|
+
if (!res.ok) await handleResponse(res);
|
|
3597
|
+
}
|
|
3598
|
+
async function searchWikiPages(instanceUrl, token, scope, id, query) {
|
|
3599
|
+
const base = instanceUrl.replace(/\/$/, "");
|
|
3600
|
+
const encodedId = encodeURIComponent(String(id));
|
|
3601
|
+
const url = `${base}/api/v4/${scope}/${encodedId}/search?scope=wiki_blobs&search=${encodeURIComponent(query)}`;
|
|
3602
|
+
const res = await fetch(url, {
|
|
3603
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
3604
|
+
});
|
|
3605
|
+
return handleResponse(res);
|
|
3606
|
+
}
|
|
3607
|
+
|
|
3608
|
+
// src/tools/wiki-tools.ts
|
|
3609
|
+
var z5 = import_plugin5.tool.schema;
|
|
3610
|
+
function makeWikiTools(ctx) {
|
|
3611
|
+
function resolveScope(args) {
|
|
3612
|
+
if (args.scope === "groups" && args.group_id) {
|
|
3613
|
+
return { scope: "groups", id: args.group_id };
|
|
3614
|
+
}
|
|
3615
|
+
return { scope: "projects", id: args.project_id };
|
|
3616
|
+
}
|
|
3617
|
+
return {
|
|
3618
|
+
gitlab_wiki_read: (0, import_plugin5.tool)({
|
|
3619
|
+
description: "Read a wiki page by slug. Returns the page content.\nUse this to read memory pages, decisions, or any wiki page.",
|
|
3620
|
+
args: {
|
|
3621
|
+
project_id: z5.string().describe('Project path (e.g., "gitlab-org/gitlab")'),
|
|
3622
|
+
slug: z5.string().describe("Wiki page slug (e.g., memory/facts)"),
|
|
3623
|
+
scope: z5.enum(["projects", "groups"]).optional().describe('Scope: "projects" (default) or "groups"'),
|
|
3624
|
+
group_id: z5.string().optional().describe("Group path, required when scope is groups")
|
|
3625
|
+
},
|
|
3626
|
+
execute: async (args) => {
|
|
3627
|
+
const auth = ctx.ensureAuth();
|
|
3628
|
+
if (!auth) throw new Error("GitLab authentication not available");
|
|
3629
|
+
const { scope, id } = resolveScope(args);
|
|
3630
|
+
try {
|
|
3631
|
+
const page = await getWikiPage(auth.instanceUrl, auth.token, scope, id, args.slug);
|
|
3632
|
+
return page.content;
|
|
3633
|
+
} catch (err) {
|
|
3634
|
+
return `Error reading wiki page: ${err.message}`;
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
}),
|
|
3638
|
+
gitlab_wiki_write: (0, import_plugin5.tool)({
|
|
3639
|
+
description: "Create or update a wiki page (upsert).\nTries to update first; if the page does not exist, creates it.\nUse this to write memory pages, decisions, or any wiki content.",
|
|
3640
|
+
args: {
|
|
3641
|
+
project_id: z5.string().describe('Project path (e.g., "gitlab-org/gitlab")'),
|
|
3642
|
+
slug: z5.string().describe("Wiki page slug (e.g., memory/facts)"),
|
|
3643
|
+
content: z5.string().describe("Full page content in Markdown"),
|
|
3644
|
+
title: z5.string().optional().describe("Page title (defaults to slug if omitted)"),
|
|
3645
|
+
scope: z5.enum(["projects", "groups"]).optional().describe('Scope: "projects" (default) or "groups"'),
|
|
3646
|
+
group_id: z5.string().optional().describe("Group path, required when scope is groups")
|
|
3647
|
+
},
|
|
3648
|
+
execute: async (args) => {
|
|
3649
|
+
const auth = ctx.ensureAuth();
|
|
3650
|
+
if (!auth) throw new Error("GitLab authentication not available");
|
|
3651
|
+
const { scope, id } = resolveScope(args);
|
|
3652
|
+
try {
|
|
3653
|
+
const page = await updateWikiPage(
|
|
3654
|
+
auth.instanceUrl,
|
|
3655
|
+
auth.token,
|
|
3656
|
+
scope,
|
|
3657
|
+
id,
|
|
3658
|
+
args.slug,
|
|
3659
|
+
args.content,
|
|
3660
|
+
args.title
|
|
3661
|
+
);
|
|
3662
|
+
return `Updated wiki page: ${page.slug}`;
|
|
3663
|
+
} catch {
|
|
3664
|
+
try {
|
|
3665
|
+
const title = args.title || args.slug.split("/").pop() || args.slug;
|
|
3666
|
+
const page = await createWikiPage(
|
|
3667
|
+
auth.instanceUrl,
|
|
3668
|
+
auth.token,
|
|
3669
|
+
scope,
|
|
3670
|
+
id,
|
|
3671
|
+
title,
|
|
3672
|
+
args.content
|
|
3673
|
+
);
|
|
3674
|
+
return `Created wiki page: ${page.slug}`;
|
|
3675
|
+
} catch (err) {
|
|
3676
|
+
return `Error writing wiki page: ${err.message}`;
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
}),
|
|
3681
|
+
gitlab_wiki_append: (0, import_plugin5.tool)({
|
|
3682
|
+
description: "Append text to an existing wiki page.\nReads the current content, appends the new text, and writes it back.\nIf the page does not exist, creates it with the provided text.",
|
|
3683
|
+
args: {
|
|
3684
|
+
project_id: z5.string().describe('Project path (e.g., "gitlab-org/gitlab")'),
|
|
3685
|
+
slug: z5.string().describe("Wiki page slug (e.g., memory/facts)"),
|
|
3686
|
+
text: z5.string().describe("Text to append to the page"),
|
|
3687
|
+
scope: z5.enum(["projects", "groups"]).optional().describe('Scope: "projects" (default) or "groups"'),
|
|
3688
|
+
group_id: z5.string().optional().describe("Group path, required when scope is groups")
|
|
3689
|
+
},
|
|
3690
|
+
execute: async (args) => {
|
|
3691
|
+
const auth = ctx.ensureAuth();
|
|
3692
|
+
if (!auth) throw new Error("GitLab authentication not available");
|
|
3693
|
+
const { scope, id } = resolveScope(args);
|
|
3694
|
+
try {
|
|
3695
|
+
const page = await getWikiPage(auth.instanceUrl, auth.token, scope, id, args.slug);
|
|
3696
|
+
const newContent = page.content + "\n" + args.text;
|
|
3697
|
+
await updateWikiPage(auth.instanceUrl, auth.token, scope, id, args.slug, newContent);
|
|
3698
|
+
return `Appended to wiki page: ${args.slug}`;
|
|
3699
|
+
} catch {
|
|
3700
|
+
try {
|
|
3701
|
+
const title = args.slug.split("/").pop() || args.slug;
|
|
3702
|
+
await createWikiPage(auth.instanceUrl, auth.token, scope, id, title, args.text);
|
|
3703
|
+
return `Created wiki page with initial content: ${args.slug}`;
|
|
3704
|
+
} catch (err) {
|
|
3705
|
+
return `Error appending to wiki page: ${err.message}`;
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
}),
|
|
3710
|
+
gitlab_wiki_list: (0, import_plugin5.tool)({
|
|
3711
|
+
description: "List wiki pages, optionally filtered by slug prefix.\nRecommended wiki structure for agent memory:\n memory/facts \u2014 persistent facts about the project\n memory/decisions \u2014 architectural and design decisions\n memory/plans \u2014 current plans and roadmaps\n memory/sessions \u2014 session logs and context\n memory/snippets \u2014 reusable code snippets and patterns\nTwo-tier resolution: tries project wiki first, then group wiki if project has no results.",
|
|
3712
|
+
args: {
|
|
3713
|
+
project_id: z5.string().describe('Project path (e.g., "gitlab-org/gitlab")'),
|
|
3714
|
+
prefix: z5.string().optional().describe('Filter pages by slug prefix (e.g., "memory/")'),
|
|
3715
|
+
scope: z5.enum(["projects", "groups"]).optional().describe('Scope: "projects" (default) or "groups"'),
|
|
3716
|
+
group_id: z5.string().optional().describe("Group path, required when scope is groups")
|
|
3717
|
+
},
|
|
3718
|
+
execute: async (args) => {
|
|
3719
|
+
const auth = ctx.ensureAuth();
|
|
3720
|
+
if (!auth) throw new Error("GitLab authentication not available");
|
|
3721
|
+
const { scope, id } = resolveScope(args);
|
|
3722
|
+
try {
|
|
3723
|
+
const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id);
|
|
3724
|
+
const filtered = args.prefix ? pages.filter((p) => p.slug.startsWith(args.prefix)) : pages;
|
|
3725
|
+
return JSON.stringify(
|
|
3726
|
+
filtered.map((p) => ({ slug: p.slug, title: p.title })),
|
|
3727
|
+
null,
|
|
3728
|
+
2
|
|
3729
|
+
);
|
|
3730
|
+
} catch (err) {
|
|
3731
|
+
return `Error listing wiki pages: ${err.message}`;
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
}),
|
|
3735
|
+
gitlab_wiki_delete: (0, import_plugin5.tool)({
|
|
3736
|
+
description: "Delete a wiki page by slug.",
|
|
3737
|
+
args: {
|
|
3738
|
+
project_id: z5.string().describe('Project path (e.g., "gitlab-org/gitlab")'),
|
|
3739
|
+
slug: z5.string().describe("Wiki page slug to delete"),
|
|
3740
|
+
scope: z5.enum(["projects", "groups"]).optional().describe('Scope: "projects" (default) or "groups"'),
|
|
3741
|
+
group_id: z5.string().optional().describe("Group path, required when scope is groups")
|
|
3742
|
+
},
|
|
3743
|
+
execute: async (args) => {
|
|
3744
|
+
const auth = ctx.ensureAuth();
|
|
3745
|
+
if (!auth) throw new Error("GitLab authentication not available");
|
|
3746
|
+
const { scope, id } = resolveScope(args);
|
|
3747
|
+
try {
|
|
3748
|
+
await deleteWikiPage(auth.instanceUrl, auth.token, scope, id, args.slug);
|
|
3749
|
+
return `Deleted wiki page: ${args.slug}`;
|
|
3750
|
+
} catch (err) {
|
|
3751
|
+
return `Error deleting wiki page: ${err.message}`;
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
}),
|
|
3755
|
+
gitlab_wiki_search: (0, import_plugin5.tool)({
|
|
3756
|
+
description: "Search wiki pages via GitLab search API.\nReturns matching page paths and content snippets.",
|
|
3757
|
+
args: {
|
|
3758
|
+
project_id: z5.string().describe('Project path (e.g., "gitlab-org/gitlab")'),
|
|
3759
|
+
query: z5.string().describe("Search query string"),
|
|
3760
|
+
scope: z5.enum(["projects", "groups"]).optional().describe('Scope: "projects" (default) or "groups"'),
|
|
3761
|
+
group_id: z5.string().optional().describe("Group path, required when scope is groups")
|
|
3762
|
+
},
|
|
3763
|
+
execute: async (args) => {
|
|
3764
|
+
const auth = ctx.ensureAuth();
|
|
3765
|
+
if (!auth) throw new Error("GitLab authentication not available");
|
|
3766
|
+
const { scope, id } = resolveScope(args);
|
|
3767
|
+
try {
|
|
3768
|
+
const results = await searchWikiPages(
|
|
3769
|
+
auth.instanceUrl,
|
|
3770
|
+
auth.token,
|
|
3771
|
+
scope,
|
|
3772
|
+
id,
|
|
3773
|
+
args.query
|
|
3774
|
+
);
|
|
3775
|
+
return JSON.stringify(
|
|
3776
|
+
results.map((r) => ({ path: r.path, snippet: r.data })),
|
|
3777
|
+
null,
|
|
3778
|
+
2
|
|
3779
|
+
);
|
|
3780
|
+
} catch (err) {
|
|
3781
|
+
return `Error searching wiki: ${err.message}`;
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
})
|
|
3785
|
+
};
|
|
3786
|
+
}
|
|
3787
|
+
|
|
3524
3788
|
// src/index.ts
|
|
3525
3789
|
var memo = /* @__PURE__ */ new Map();
|
|
3526
3790
|
var plugin = async (input) => {
|
|
@@ -3674,7 +3938,8 @@ var plugin = async (input) => {
|
|
|
3674
3938
|
...makeFlowTools(ctx),
|
|
3675
3939
|
...makeMcpTools(() => cachedAgents),
|
|
3676
3940
|
...makeCatalogCrudTools(ctx),
|
|
3677
|
-
...makeCatalogItemTools(ctx)
|
|
3941
|
+
...makeCatalogItemTools(ctx),
|
|
3942
|
+
...makeWikiTools(ctx)
|
|
3678
3943
|
}
|
|
3679
3944
|
};
|
|
3680
3945
|
};
|