opencode-gitlab-dap 1.7.1 → 1.8.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 +19 -23
- package/dist/index.cjs +281 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +281 -125
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -110,7 +110,7 @@ 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
|
+
### 28 Tools
|
|
114
114
|
|
|
115
115
|
#### DAP Tools (20)
|
|
116
116
|
|
|
@@ -137,33 +137,29 @@ The vendored `flow_v2.json` schema from GitLab Rails powers client-side validati
|
|
|
137
137
|
| `gitlab_get_workflow_status` | Monitor workflow execution status and logs |
|
|
138
138
|
| `gitlab_list_project_mcp_servers` | List MCP servers available for project agents |
|
|
139
139
|
|
|
140
|
-
####
|
|
140
|
+
#### Project Knowledge Tools (8)
|
|
141
141
|
|
|
142
|
-
Persistent project memory and
|
|
142
|
+
Persistent project memory and reusable skills. Knowledge is stored in GitLab project/group wikis but tools abstract the storage — the agent works with facts, decisions, patterns, and skills.
|
|
143
143
|
|
|
144
|
-
|
|
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) |
|
|
144
|
+
##### Memory Tools
|
|
152
145
|
|
|
153
|
-
|
|
146
|
+
| Tool | Description |
|
|
147
|
+
| --------------------------- | -------------------------------------------------------- |
|
|
148
|
+
| `gitlab_memory_load` | Load project memory (facts, decisions, patterns, or all) |
|
|
149
|
+
| `gitlab_memory_record` | Record a fact, decision, or pattern (auto-timestamped) |
|
|
150
|
+
| `gitlab_memory_recall` | Search project knowledge for relevant information |
|
|
151
|
+
| `gitlab_memory_log_session` | Log a session summary with learnings |
|
|
154
152
|
|
|
155
|
-
#####
|
|
153
|
+
##### Skill Tools
|
|
156
154
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
└── <name> # Individual skill pages
|
|
166
|
-
```
|
|
155
|
+
| Tool | Description |
|
|
156
|
+
| ---------------------- | ---------------------------------------------- |
|
|
157
|
+
| `gitlab_skill_list` | List available skills (and optionally drafts) |
|
|
158
|
+
| `gitlab_skill_load` | Load a skill (published first, then drafts) |
|
|
159
|
+
| `gitlab_skill_save` | Create or update a skill (supports draft flag) |
|
|
160
|
+
| `gitlab_skill_promote` | Promote a draft skill to published |
|
|
161
|
+
|
|
162
|
+
All tools support project scope (default) and group scope (`scope="groups"`).
|
|
167
163
|
|
|
168
164
|
### Dynamic Refresh
|
|
169
165
|
|
package/dist/index.cjs
CHANGED
|
@@ -2254,9 +2254,12 @@ prompts:
|
|
|
2254
2254
|
user: "Fix this vulnerability: {{vuln_data}}"
|
|
2255
2255
|
placeholder: history
|
|
2256
2256
|
\`\`\``;
|
|
2257
|
-
var
|
|
2258
|
-
|
|
2259
|
-
|
|
2257
|
+
var PROJECT_KNOWLEDGE_HINT = `## Project Knowledge
|
|
2258
|
+
This project may have persistent memory and skills available via knowledge tools.
|
|
2259
|
+
Use gitlab_memory_load to check for existing project context (facts, decisions, patterns).
|
|
2260
|
+
Use gitlab_skill_list to discover available task-specific skills.
|
|
2261
|
+
When you learn something new about the project, use gitlab_memory_record to preserve it.
|
|
2262
|
+
When you complete a significant task, consider using gitlab_memory_log_session to log learnings.`;
|
|
2260
2263
|
|
|
2261
2264
|
// src/hooks.ts
|
|
2262
2265
|
function buildFlowSubagentPrompt(flow, projectPath, projectUrl) {
|
|
@@ -2416,7 +2419,7 @@ function makeSystemTransformHook(flowAgents, getAuthCache) {
|
|
|
2416
2419
|
}
|
|
2417
2420
|
if (getAuthCache()) {
|
|
2418
2421
|
output.system.push(AGENT_CREATION_GUIDELINES);
|
|
2419
|
-
output.system.push(
|
|
2422
|
+
output.system.push(PROJECT_KNOWLEDGE_HINT);
|
|
2420
2423
|
}
|
|
2421
2424
|
};
|
|
2422
2425
|
}
|
|
@@ -3525,7 +3528,7 @@ function makeMcpTools(getCachedAgents) {
|
|
|
3525
3528
|
};
|
|
3526
3529
|
}
|
|
3527
3530
|
|
|
3528
|
-
// src/tools/
|
|
3531
|
+
// src/tools/memory-tools.ts
|
|
3529
3532
|
var import_plugin5 = require("@opencode-ai/plugin");
|
|
3530
3533
|
|
|
3531
3534
|
// src/wiki.ts
|
|
@@ -3606,192 +3609,344 @@ async function searchWikiPages(instanceUrl, token, scope, id, query) {
|
|
|
3606
3609
|
return handleResponse(res);
|
|
3607
3610
|
}
|
|
3608
3611
|
|
|
3609
|
-
// src/tools/
|
|
3612
|
+
// src/tools/memory-tools.ts
|
|
3610
3613
|
var z5 = import_plugin5.tool.schema;
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3614
|
+
var PREFIX = "agents";
|
|
3615
|
+
var MEMORY_SLUGS = {
|
|
3616
|
+
all: [`${PREFIX}/memory/facts`, `${PREFIX}/memory/decisions`, `${PREFIX}/memory/patterns`],
|
|
3617
|
+
facts: [`${PREFIX}/memory/facts`],
|
|
3618
|
+
decisions: [`${PREFIX}/memory/decisions`],
|
|
3619
|
+
patterns: [`${PREFIX}/memory/patterns`]
|
|
3620
|
+
};
|
|
3621
|
+
var RECORD_SLUG = {
|
|
3622
|
+
fact: `${PREFIX}/memory/facts`,
|
|
3623
|
+
decision: `${PREFIX}/memory/decisions`,
|
|
3624
|
+
pattern: `${PREFIX}/memory/patterns`
|
|
3625
|
+
};
|
|
3626
|
+
function today() {
|
|
3627
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3628
|
+
}
|
|
3629
|
+
function slugify(text) {
|
|
3630
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, 60);
|
|
3631
|
+
}
|
|
3632
|
+
async function safeRead(instanceUrl, token, scope, id, slug) {
|
|
3633
|
+
try {
|
|
3634
|
+
const page = await getWikiPage(instanceUrl, token, scope, id, slug);
|
|
3635
|
+
return page.content;
|
|
3636
|
+
} catch {
|
|
3637
|
+
return null;
|
|
3617
3638
|
}
|
|
3639
|
+
}
|
|
3640
|
+
async function appendToPage(instanceUrl, token, scope, id, slug, newContent) {
|
|
3641
|
+
const existing = await safeRead(instanceUrl, token, scope, id, slug);
|
|
3642
|
+
if (existing !== null) {
|
|
3643
|
+
await updateWikiPage(instanceUrl, token, scope, id, slug, existing + "\n\n" + newContent);
|
|
3644
|
+
} else {
|
|
3645
|
+
await createWikiPage(instanceUrl, token, scope, id, slug, newContent);
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3648
|
+
function resolveScope(args) {
|
|
3649
|
+
if (args.scope === "groups" && args.group_id) {
|
|
3650
|
+
return { scope: "groups", id: args.group_id };
|
|
3651
|
+
}
|
|
3652
|
+
return { scope: "projects", id: args.project_id };
|
|
3653
|
+
}
|
|
3654
|
+
function makeMemoryTools(ctx) {
|
|
3618
3655
|
return {
|
|
3619
|
-
|
|
3620
|
-
description: "
|
|
3656
|
+
gitlab_memory_load: (0, import_plugin5.tool)({
|
|
3657
|
+
description: "Load project memory to understand context, known facts, past decisions, and observed patterns.\nUse this at the start of complex tasks to check what is already known about the project.\nReturns accumulated knowledge from previous sessions.",
|
|
3621
3658
|
args: {
|
|
3622
|
-
project_id: z5.string().describe(
|
|
3623
|
-
|
|
3624
|
-
),
|
|
3625
|
-
|
|
3626
|
-
scope: z5.enum(["projects", "groups"]).optional().describe('Scope: "projects" (default) or "groups"'),
|
|
3627
|
-
group_id: z5.string().optional().describe("Group path, required when scope is groups")
|
|
3659
|
+
project_id: z5.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
|
|
3660
|
+
type: z5.enum(["all", "facts", "decisions", "patterns"]).optional().describe('Which memory to load: "all" (default), "facts", "decisions", or "patterns"'),
|
|
3661
|
+
scope: z5.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
3662
|
+
group_id: z5.string().optional().describe("Group path (required when scope is groups)")
|
|
3628
3663
|
},
|
|
3629
3664
|
execute: async (args) => {
|
|
3630
3665
|
const auth = ctx.ensureAuth();
|
|
3631
3666
|
if (!auth) throw new Error("GitLab authentication not available");
|
|
3632
3667
|
const { scope, id } = resolveScope(args);
|
|
3668
|
+
const memType = args.type ?? "all";
|
|
3669
|
+
const slugs = MEMORY_SLUGS[memType];
|
|
3670
|
+
if (!slugs) return `Unknown memory type: ${memType}`;
|
|
3671
|
+
const sections = [];
|
|
3672
|
+
for (const slug of slugs) {
|
|
3673
|
+
const content = await safeRead(auth.instanceUrl, auth.token, scope, id, slug);
|
|
3674
|
+
if (content) {
|
|
3675
|
+
const label = slug.split("/").pop();
|
|
3676
|
+
sections.push(`## ${label.charAt(0).toUpperCase() + label.slice(1)}
|
|
3677
|
+
|
|
3678
|
+
${content}`);
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
if (sections.length === 0)
|
|
3682
|
+
return "No project memory found. Use gitlab_memory_record to start building project knowledge.";
|
|
3683
|
+
return sections.join("\n\n---\n\n");
|
|
3684
|
+
}
|
|
3685
|
+
}),
|
|
3686
|
+
gitlab_memory_record: (0, import_plugin5.tool)({
|
|
3687
|
+
description: "Record a fact, decision, or pattern in project memory.\nFacts: stable truths about the project (e.g., deploy targets, tech stack, team conventions).\nDecisions: architectural choices with reasoning (why X was chosen over Y).\nPatterns: recurring observations that may evolve into skills over time.\nEntries are automatically timestamped and appended to the appropriate memory page.\nMultiple facts can be recorded in a single call by separating them with newlines.",
|
|
3688
|
+
args: {
|
|
3689
|
+
project_id: z5.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
|
|
3690
|
+
type: z5.enum(["fact", "decision", "pattern"]).describe("Type of knowledge to record"),
|
|
3691
|
+
content: z5.string().describe("The knowledge to record (markdown)"),
|
|
3692
|
+
scope: z5.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
3693
|
+
group_id: z5.string().optional().describe("Group path (required when scope is groups)")
|
|
3694
|
+
},
|
|
3695
|
+
execute: async (args) => {
|
|
3696
|
+
const auth = ctx.ensureAuth();
|
|
3697
|
+
if (!auth) throw new Error("GitLab authentication not available");
|
|
3698
|
+
const { scope, id } = resolveScope(args);
|
|
3699
|
+
const slug = RECORD_SLUG[args.type];
|
|
3700
|
+
if (!slug) return `Unknown memory type: ${args.type}`;
|
|
3701
|
+
const date = today();
|
|
3702
|
+
let formatted;
|
|
3703
|
+
if (args.type === "decision") {
|
|
3704
|
+
const firstLine = args.content.split("\n")[0];
|
|
3705
|
+
const rest = args.content.split("\n").slice(1).join("\n").trim();
|
|
3706
|
+
formatted = `## ${date}: ${firstLine}${rest ? "\n" + rest : ""}`;
|
|
3707
|
+
} else if (args.type === "fact") {
|
|
3708
|
+
formatted = `## ${date}
|
|
3709
|
+
- ${args.content.replace(/\n/g, "\n- ")}`;
|
|
3710
|
+
} else {
|
|
3711
|
+
formatted = `## ${date}
|
|
3712
|
+
${args.content}`;
|
|
3713
|
+
}
|
|
3633
3714
|
try {
|
|
3634
|
-
|
|
3635
|
-
return
|
|
3715
|
+
await appendToPage(auth.instanceUrl, auth.token, scope, id, slug, formatted);
|
|
3716
|
+
return `Recorded ${args.type} in project memory.`;
|
|
3636
3717
|
} catch (err) {
|
|
3637
|
-
return `Error
|
|
3718
|
+
return `Error recording ${args.type}: ${err.message}`;
|
|
3638
3719
|
}
|
|
3639
3720
|
}
|
|
3640
3721
|
}),
|
|
3641
|
-
|
|
3642
|
-
description: "
|
|
3722
|
+
gitlab_memory_recall: (0, import_plugin5.tool)({
|
|
3723
|
+
description: "Search project knowledge for relevant information.\nSearches across all memory pages, session logs, and skills.\nUse this to check if something is already known before investigating.",
|
|
3643
3724
|
args: {
|
|
3644
|
-
project_id: z5.string().describe(
|
|
3645
|
-
|
|
3646
|
-
),
|
|
3647
|
-
|
|
3648
|
-
content: z5.string().describe("Full page content in Markdown"),
|
|
3649
|
-
title: z5.string().optional().describe("Page title (defaults to slug if omitted)"),
|
|
3650
|
-
scope: z5.enum(["projects", "groups"]).optional().describe('Scope: "projects" (default) or "groups"'),
|
|
3651
|
-
group_id: z5.string().optional().describe("Group path, required when scope is groups")
|
|
3725
|
+
project_id: z5.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
|
|
3726
|
+
query: z5.string().describe("What to search for"),
|
|
3727
|
+
scope: z5.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
3728
|
+
group_id: z5.string().optional().describe("Group path (required when scope is groups)")
|
|
3652
3729
|
},
|
|
3653
3730
|
execute: async (args) => {
|
|
3654
3731
|
const auth = ctx.ensureAuth();
|
|
3655
3732
|
if (!auth) throw new Error("GitLab authentication not available");
|
|
3656
3733
|
const { scope, id } = resolveScope(args);
|
|
3657
3734
|
try {
|
|
3658
|
-
const
|
|
3735
|
+
const results = await searchWikiPages(
|
|
3659
3736
|
auth.instanceUrl,
|
|
3660
3737
|
auth.token,
|
|
3661
3738
|
scope,
|
|
3662
3739
|
id,
|
|
3663
|
-
args.
|
|
3664
|
-
args.content,
|
|
3665
|
-
args.title
|
|
3740
|
+
args.query
|
|
3666
3741
|
);
|
|
3667
|
-
return `
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
const
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
id,
|
|
3676
|
-
title,
|
|
3677
|
-
args.content
|
|
3678
|
-
);
|
|
3679
|
-
return `Created wiki page: ${page.slug}`;
|
|
3680
|
-
} catch (err) {
|
|
3681
|
-
return `Error writing wiki page: ${err.message}`;
|
|
3682
|
-
}
|
|
3742
|
+
if (results.length === 0) return `No knowledge found matching "${args.query}".`;
|
|
3743
|
+
return results.map((r) => {
|
|
3744
|
+
const category = r.path.includes("/memory/") ? "memory" : r.path.includes("/skills") ? "skill" : "other";
|
|
3745
|
+
const snippet = (r.data ?? "").slice(0, 200).replace(/\n/g, " ");
|
|
3746
|
+
return `[${category}] ${r.path}: ${snippet}`;
|
|
3747
|
+
}).join("\n\n");
|
|
3748
|
+
} catch (err) {
|
|
3749
|
+
return `Error searching knowledge: ${err.message}`;
|
|
3683
3750
|
}
|
|
3684
3751
|
}
|
|
3685
3752
|
}),
|
|
3686
|
-
|
|
3687
|
-
description: "
|
|
3753
|
+
gitlab_memory_log_session: (0, import_plugin5.tool)({
|
|
3754
|
+
description: "Log a session summary including what was accomplished, what was learned, and any suggestions.\nUse this at the end of significant work sessions to preserve context for future sessions.",
|
|
3688
3755
|
args: {
|
|
3689
|
-
project_id: z5.string().describe(
|
|
3690
|
-
|
|
3756
|
+
project_id: z5.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
|
|
3757
|
+
title: z5.string().describe('Brief session title (e.g., "fix-ai-gateway-healthcheck")'),
|
|
3758
|
+
summary: z5.string().describe(
|
|
3759
|
+
"Session summary in markdown (what happened, what was learned, what went wrong)"
|
|
3691
3760
|
),
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
scope: z5.enum(["projects", "groups"]).optional().describe('Scope: "projects" (default) or "groups"'),
|
|
3695
|
-
group_id: z5.string().optional().describe("Group path, required when scope is groups")
|
|
3761
|
+
scope: z5.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
3762
|
+
group_id: z5.string().optional().describe("Group path (required when scope is groups)")
|
|
3696
3763
|
},
|
|
3697
3764
|
execute: async (args) => {
|
|
3698
3765
|
const auth = ctx.ensureAuth();
|
|
3699
3766
|
if (!auth) throw new Error("GitLab authentication not available");
|
|
3700
3767
|
const { scope, id } = resolveScope(args);
|
|
3768
|
+
const date = today();
|
|
3769
|
+
const slug = `${PREFIX}/memory/sessions/${date}-${slugify(args.title)}`;
|
|
3701
3770
|
try {
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
return `Error appending to wiki page: ${err.message}`;
|
|
3771
|
+
await createWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.summary);
|
|
3772
|
+
return `Session logged: ${slug}`;
|
|
3773
|
+
} catch (err) {
|
|
3774
|
+
if (err.message?.includes("already exists") || err.message?.includes("422")) {
|
|
3775
|
+
try {
|
|
3776
|
+
await updateWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.summary);
|
|
3777
|
+
return `Session log updated: ${slug}`;
|
|
3778
|
+
} catch (err2) {
|
|
3779
|
+
return `Error logging session: ${err2.message}`;
|
|
3780
|
+
}
|
|
3713
3781
|
}
|
|
3782
|
+
return `Error logging session: ${err.message}`;
|
|
3714
3783
|
}
|
|
3715
3784
|
}
|
|
3716
|
-
})
|
|
3717
|
-
|
|
3718
|
-
|
|
3785
|
+
})
|
|
3786
|
+
};
|
|
3787
|
+
}
|
|
3788
|
+
|
|
3789
|
+
// src/tools/skill-tools.ts
|
|
3790
|
+
var import_plugin6 = require("@opencode-ai/plugin");
|
|
3791
|
+
var z6 = import_plugin6.tool.schema;
|
|
3792
|
+
var PREFIX2 = "agents";
|
|
3793
|
+
var SKILLS_PREFIX = `${PREFIX2}/skills`;
|
|
3794
|
+
var DRAFTS_PREFIX = `${PREFIX2}/skills-drafts`;
|
|
3795
|
+
function resolveScope2(args) {
|
|
3796
|
+
if (args.scope === "groups" && args.group_id) {
|
|
3797
|
+
return { scope: "groups", id: args.group_id };
|
|
3798
|
+
}
|
|
3799
|
+
return { scope: "projects", id: args.project_id };
|
|
3800
|
+
}
|
|
3801
|
+
function makeSkillTools(ctx) {
|
|
3802
|
+
return {
|
|
3803
|
+
gitlab_skill_list: (0, import_plugin6.tool)({
|
|
3804
|
+
description: "List available project skills and optionally draft skills.\nSkills define step-by-step procedures for common tasks (e.g., incident retros, debugging, deployments).",
|
|
3719
3805
|
args: {
|
|
3720
|
-
project_id:
|
|
3721
|
-
|
|
3722
|
-
),
|
|
3723
|
-
|
|
3724
|
-
scope: z5.enum(["projects", "groups"]).optional().describe('Scope: "projects" (default) or "groups"'),
|
|
3725
|
-
group_id: z5.string().optional().describe("Group path, required when scope is groups")
|
|
3806
|
+
project_id: z6.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
|
|
3807
|
+
include_drafts: z6.boolean().optional().describe("Also list draft skills (default: false)"),
|
|
3808
|
+
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
3809
|
+
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
3726
3810
|
},
|
|
3727
3811
|
execute: async (args) => {
|
|
3728
3812
|
const auth = ctx.ensureAuth();
|
|
3729
3813
|
if (!auth) throw new Error("GitLab authentication not available");
|
|
3730
|
-
const { scope, id } =
|
|
3814
|
+
const { scope, id } = resolveScope2(args);
|
|
3731
3815
|
try {
|
|
3732
3816
|
const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id);
|
|
3733
|
-
const
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
);
|
|
3817
|
+
const indexSlug = `${SKILLS_PREFIX}/index`;
|
|
3818
|
+
const skills = pages.filter((p) => p.slug.startsWith(`${SKILLS_PREFIX}/`) && p.slug !== indexSlug).map((p) => ({
|
|
3819
|
+
name: p.slug.slice(SKILLS_PREFIX.length + 1),
|
|
3820
|
+
title: p.title,
|
|
3821
|
+
draft: false
|
|
3822
|
+
}));
|
|
3823
|
+
let drafts = [];
|
|
3824
|
+
if (args.include_drafts) {
|
|
3825
|
+
const draftsIndexSlug = `${DRAFTS_PREFIX}/index`;
|
|
3826
|
+
drafts = pages.filter((p) => p.slug.startsWith(`${DRAFTS_PREFIX}/`) && p.slug !== draftsIndexSlug).map((p) => ({
|
|
3827
|
+
name: p.slug.slice(DRAFTS_PREFIX.length + 1),
|
|
3828
|
+
title: p.title,
|
|
3829
|
+
draft: true
|
|
3830
|
+
}));
|
|
3831
|
+
}
|
|
3832
|
+
const all = [...skills, ...drafts];
|
|
3833
|
+
if (all.length === 0) return "No skills found. Use gitlab_skill_save to create one.";
|
|
3834
|
+
return JSON.stringify(all, null, 2);
|
|
3739
3835
|
} catch (err) {
|
|
3740
|
-
return `Error listing
|
|
3836
|
+
return `Error listing skills: ${err.message}`;
|
|
3741
3837
|
}
|
|
3742
3838
|
}
|
|
3743
3839
|
}),
|
|
3744
|
-
|
|
3745
|
-
description: "
|
|
3840
|
+
gitlab_skill_load: (0, import_plugin6.tool)({
|
|
3841
|
+
description: "Load a specific skill by name.\nSkills contain step-by-step instructions for common tasks.\nChecks published skills first, then falls back to draft skills.",
|
|
3746
3842
|
args: {
|
|
3747
|
-
project_id:
|
|
3748
|
-
|
|
3749
|
-
),
|
|
3750
|
-
|
|
3751
|
-
scope: z5.enum(["projects", "groups"]).optional().describe('Scope: "projects" (default) or "groups"'),
|
|
3752
|
-
group_id: z5.string().optional().describe("Group path, required when scope is groups")
|
|
3843
|
+
project_id: z6.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
|
|
3844
|
+
name: z6.string().describe('Skill name (e.g., "incident-retro", "helm-rollback")'),
|
|
3845
|
+
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
3846
|
+
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
3753
3847
|
},
|
|
3754
3848
|
execute: async (args) => {
|
|
3755
3849
|
const auth = ctx.ensureAuth();
|
|
3756
3850
|
if (!auth) throw new Error("GitLab authentication not available");
|
|
3757
|
-
const { scope, id } =
|
|
3851
|
+
const { scope, id } = resolveScope2(args);
|
|
3758
3852
|
try {
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3853
|
+
const page = await getWikiPage(
|
|
3854
|
+
auth.instanceUrl,
|
|
3855
|
+
auth.token,
|
|
3856
|
+
scope,
|
|
3857
|
+
id,
|
|
3858
|
+
`${SKILLS_PREFIX}/${args.name}`
|
|
3859
|
+
);
|
|
3860
|
+
return page.content;
|
|
3861
|
+
} catch {
|
|
3862
|
+
try {
|
|
3863
|
+
const draft = await getWikiPage(
|
|
3864
|
+
auth.instanceUrl,
|
|
3865
|
+
auth.token,
|
|
3866
|
+
scope,
|
|
3867
|
+
id,
|
|
3868
|
+
`${DRAFTS_PREFIX}/${args.name}`
|
|
3869
|
+
);
|
|
3870
|
+
return `[DRAFT SKILL]
|
|
3871
|
+
|
|
3872
|
+
${draft.content}`;
|
|
3873
|
+
} catch {
|
|
3874
|
+
return `Skill "${args.name}" not found. Use gitlab_skill_list to see available skills.`;
|
|
3875
|
+
}
|
|
3763
3876
|
}
|
|
3764
3877
|
}
|
|
3765
3878
|
}),
|
|
3766
|
-
|
|
3767
|
-
description: "
|
|
3879
|
+
gitlab_skill_save: (0, import_plugin6.tool)({
|
|
3880
|
+
description: "Create or update a skill.\nSkills define step-by-step procedures for common tasks.\nUse draft=true for skills that haven't been proven yet.",
|
|
3768
3881
|
args: {
|
|
3769
|
-
project_id:
|
|
3770
|
-
|
|
3771
|
-
),
|
|
3772
|
-
|
|
3773
|
-
scope:
|
|
3774
|
-
group_id:
|
|
3882
|
+
project_id: z6.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
|
|
3883
|
+
name: z6.string().describe('Skill name (e.g., "incident-retro")'),
|
|
3884
|
+
content: z6.string().describe("Skill content in markdown"),
|
|
3885
|
+
draft: z6.boolean().optional().describe("Save as draft skill (default: false)"),
|
|
3886
|
+
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
3887
|
+
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
3775
3888
|
},
|
|
3776
3889
|
execute: async (args) => {
|
|
3777
3890
|
const auth = ctx.ensureAuth();
|
|
3778
3891
|
if (!auth) throw new Error("GitLab authentication not available");
|
|
3779
|
-
const { scope, id } =
|
|
3892
|
+
const { scope, id } = resolveScope2(args);
|
|
3893
|
+
const prefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
|
|
3894
|
+
const slug = `${prefix}/${args.name}`;
|
|
3780
3895
|
try {
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
id,
|
|
3786
|
-
args.
|
|
3787
|
-
)
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3896
|
+
await updateWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.content);
|
|
3897
|
+
return `Updated ${args.draft ? "draft " : ""}skill: ${args.name}`;
|
|
3898
|
+
} catch {
|
|
3899
|
+
try {
|
|
3900
|
+
await createWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.content);
|
|
3901
|
+
return `Created ${args.draft ? "draft " : ""}skill: ${args.name}`;
|
|
3902
|
+
} catch (err) {
|
|
3903
|
+
return `Error saving skill: ${err.message}`;
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
}),
|
|
3908
|
+
gitlab_skill_promote: (0, import_plugin6.tool)({
|
|
3909
|
+
description: "Promote a draft skill to published.\nMoves the skill from the drafts directory to the published skills directory.",
|
|
3910
|
+
args: {
|
|
3911
|
+
project_id: z6.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
|
|
3912
|
+
name: z6.string().describe("Skill name to promote"),
|
|
3913
|
+
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
3914
|
+
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
3915
|
+
},
|
|
3916
|
+
execute: async (args) => {
|
|
3917
|
+
const auth = ctx.ensureAuth();
|
|
3918
|
+
if (!auth) throw new Error("GitLab authentication not available");
|
|
3919
|
+
const { scope, id } = resolveScope2(args);
|
|
3920
|
+
const draftSlug = `${DRAFTS_PREFIX}/${args.name}`;
|
|
3921
|
+
const publishedSlug = `${SKILLS_PREFIX}/${args.name}`;
|
|
3922
|
+
try {
|
|
3923
|
+
const draft = await getWikiPage(auth.instanceUrl, auth.token, scope, id, draftSlug);
|
|
3924
|
+
try {
|
|
3925
|
+
await updateWikiPage(
|
|
3926
|
+
auth.instanceUrl,
|
|
3927
|
+
auth.token,
|
|
3928
|
+
scope,
|
|
3929
|
+
id,
|
|
3930
|
+
publishedSlug,
|
|
3931
|
+
draft.content
|
|
3932
|
+
);
|
|
3933
|
+
} catch {
|
|
3934
|
+
await createWikiPage(
|
|
3935
|
+
auth.instanceUrl,
|
|
3936
|
+
auth.token,
|
|
3937
|
+
scope,
|
|
3938
|
+
id,
|
|
3939
|
+
publishedSlug,
|
|
3940
|
+
draft.content
|
|
3941
|
+
);
|
|
3942
|
+
}
|
|
3943
|
+
await deleteWikiPage(auth.instanceUrl, auth.token, scope, id, draftSlug);
|
|
3944
|
+
return `Promoted skill "${args.name}" from draft to published.`;
|
|
3793
3945
|
} catch (err) {
|
|
3794
|
-
|
|
3946
|
+
if (err.message?.includes("not found") || err.message?.includes("404")) {
|
|
3947
|
+
return `Draft skill "${args.name}" not found. Use gitlab_skill_list(include_drafts=true) to see available drafts.`;
|
|
3948
|
+
}
|
|
3949
|
+
return `Error promoting skill: ${err.message}`;
|
|
3795
3950
|
}
|
|
3796
3951
|
}
|
|
3797
3952
|
})
|
|
@@ -3952,7 +4107,8 @@ var plugin = async (input) => {
|
|
|
3952
4107
|
...makeMcpTools(() => cachedAgents),
|
|
3953
4108
|
...makeCatalogCrudTools(ctx),
|
|
3954
4109
|
...makeCatalogItemTools(ctx),
|
|
3955
|
-
...
|
|
4110
|
+
...makeMemoryTools(ctx),
|
|
4111
|
+
...makeSkillTools(ctx)
|
|
3956
4112
|
}
|
|
3957
4113
|
};
|
|
3958
4114
|
};
|