opencode-gitlab-dap 1.12.0 → 1.13.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 +13 -8
- package/dist/index.cjs +338 -90
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +338 -90
- 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
|
+
### 34 Tools
|
|
114
114
|
|
|
115
115
|
#### DAP Tools (20)
|
|
116
116
|
|
|
@@ -157,13 +157,18 @@ Say **"bootstrap project memory"** to automatically inspect a project and build
|
|
|
157
157
|
|
|
158
158
|
##### Skill Tools
|
|
159
159
|
|
|
160
|
-
| Tool
|
|
161
|
-
|
|
|
162
|
-
| `gitlab_skill_list`
|
|
163
|
-
| `gitlab_skill_load`
|
|
164
|
-
| `gitlab_skill_save`
|
|
165
|
-
| `gitlab_skill_promote`
|
|
166
|
-
|
|
160
|
+
| Tool | Description |
|
|
161
|
+
| ----------------------- | --------------------------------------------------------- |
|
|
162
|
+
| `gitlab_skill_list` | List skills with auto-rebuilding index |
|
|
163
|
+
| `gitlab_skill_load` | Load a skill (SKILL page + available references) |
|
|
164
|
+
| `gitlab_skill_save` | Create/update a skill with required description for index |
|
|
165
|
+
| `gitlab_skill_promote` | Promote a draft skill to published (moves all pages) |
|
|
166
|
+
| `gitlab_skill_discover` | Search group wiki for team-shared skills |
|
|
167
|
+
| `gitlab_skill_install` | Copy a skill from group wiki to project wiki |
|
|
168
|
+
| `gitlab_skill_delete` | Delete a skill and remove from index |
|
|
169
|
+
|
|
170
|
+
Each skill is a directory: `agents/skills/<name>/SKILL` with optional `references/` subpages.
|
|
171
|
+
Skills are indexed in `agents/skills/index` for fast discovery.
|
|
167
172
|
All tools support project scope (default) and group scope (`scope="groups"`).
|
|
168
173
|
|
|
169
174
|
### Dynamic Refresh
|
package/dist/index.cjs
CHANGED
|
@@ -3636,8 +3636,11 @@ var PROJECT_ID_DESC = 'FULL project path with namespace (e.g., "gitlab-org/gitla
|
|
|
3636
3636
|
function today() {
|
|
3637
3637
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3638
3638
|
}
|
|
3639
|
+
function shortHash() {
|
|
3640
|
+
return Math.random().toString(36).slice(2, 8);
|
|
3641
|
+
}
|
|
3639
3642
|
function slugify(text) {
|
|
3640
|
-
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0,
|
|
3643
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, 50);
|
|
3641
3644
|
}
|
|
3642
3645
|
function titleFromContent(content) {
|
|
3643
3646
|
const first = content.split("\n")[0].replace(/^[#*\->\s]+/, "").trim();
|
|
@@ -3725,7 +3728,7 @@ ${entries}`
|
|
|
3725
3728
|
if (!dir) return `Unknown memory type: ${args.type}`;
|
|
3726
3729
|
const date = today();
|
|
3727
3730
|
const title = titleFromContent(args.content);
|
|
3728
|
-
const slug = `${dir}/${date}-${slugify(title)}`;
|
|
3731
|
+
const slug = `${dir}/${date}-${slugify(title)}-${shortHash()}`;
|
|
3729
3732
|
const header = `*Recorded: ${date} | Type: ${args.type}*
|
|
3730
3733
|
|
|
3731
3734
|
`;
|
|
@@ -3930,7 +3933,7 @@ ${e.content}`).join("\n\n---\n\n");
|
|
|
3930
3933
|
const auth = authAndValidate(args.project_id);
|
|
3931
3934
|
const { scope, id } = resolveScope(args);
|
|
3932
3935
|
const date = today();
|
|
3933
|
-
const slug = `${PREFIX}/memory/sessions/${date}-${slugify(args.title)}`;
|
|
3936
|
+
const slug = `${PREFIX}/memory/sessions/${date}-${slugify(args.title)}-${shortHash()}`;
|
|
3934
3937
|
try {
|
|
3935
3938
|
await createWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.summary);
|
|
3936
3939
|
return `Session logged: ${slug}`;
|
|
@@ -3948,6 +3951,9 @@ var z6 = import_plugin6.tool.schema;
|
|
|
3948
3951
|
var PREFIX2 = "agents";
|
|
3949
3952
|
var SKILLS_PREFIX = `${PREFIX2}/skills`;
|
|
3950
3953
|
var DRAFTS_PREFIX = `${PREFIX2}/skills-drafts`;
|
|
3954
|
+
var SKILLS_INDEX = `${SKILLS_PREFIX}/index`;
|
|
3955
|
+
var DRAFTS_INDEX = `${DRAFTS_PREFIX}/index`;
|
|
3956
|
+
var PROJECT_ID_DESC2 = 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.';
|
|
3951
3957
|
function resolveScope2(args) {
|
|
3952
3958
|
if (args.scope === "groups" && args.group_id) {
|
|
3953
3959
|
return { scope: "groups", id: args.group_id };
|
|
@@ -3960,6 +3966,117 @@ function validateProjectId2(projectId) {
|
|
|
3960
3966
|
}
|
|
3961
3967
|
return null;
|
|
3962
3968
|
}
|
|
3969
|
+
function parseIndex(content) {
|
|
3970
|
+
const entries = [];
|
|
3971
|
+
const blocks = content.split(/^## /m).filter(Boolean);
|
|
3972
|
+
for (const block of blocks) {
|
|
3973
|
+
const lines = block.trim().split("\n");
|
|
3974
|
+
const name = lines[0].trim();
|
|
3975
|
+
if (!name) continue;
|
|
3976
|
+
const rest = lines.slice(1).join("\n").trim();
|
|
3977
|
+
const descLines = [];
|
|
3978
|
+
let source;
|
|
3979
|
+
for (const line of rest.split("\n")) {
|
|
3980
|
+
if (line.startsWith("Source:")) {
|
|
3981
|
+
source = line.slice(7).trim();
|
|
3982
|
+
} else if (line.trim()) {
|
|
3983
|
+
descLines.push(line);
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
entries.push({ name, description: descLines.join("\n"), source, draft: false });
|
|
3987
|
+
}
|
|
3988
|
+
return entries;
|
|
3989
|
+
}
|
|
3990
|
+
function formatIndex(entries) {
|
|
3991
|
+
return entries.map((e) => {
|
|
3992
|
+
let block = `## ${e.name}
|
|
3993
|
+
${e.description}`;
|
|
3994
|
+
if (e.source) block += `
|
|
3995
|
+
Source: ${e.source}`;
|
|
3996
|
+
return block;
|
|
3997
|
+
}).join("\n\n");
|
|
3998
|
+
}
|
|
3999
|
+
async function readIndex(instanceUrl, token, scope, id, indexSlug) {
|
|
4000
|
+
try {
|
|
4001
|
+
const page = await getWikiPage(instanceUrl, token, scope, id, indexSlug);
|
|
4002
|
+
return parseIndex(page.content);
|
|
4003
|
+
} catch {
|
|
4004
|
+
return [];
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
async function writeIndex(instanceUrl, token, scope, id, indexSlug, entries) {
|
|
4008
|
+
const content = formatIndex(entries);
|
|
4009
|
+
try {
|
|
4010
|
+
await updateWikiPage(instanceUrl, token, scope, id, indexSlug, content);
|
|
4011
|
+
} catch {
|
|
4012
|
+
await createWikiPage(instanceUrl, token, scope, id, indexSlug, content || "# Skills Index");
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
async function upsertIndexEntry(instanceUrl, token, scope, id, indexSlug, entry) {
|
|
4016
|
+
const entries = await readIndex(instanceUrl, token, scope, id, indexSlug);
|
|
4017
|
+
const idx = entries.findIndex((e) => e.name === entry.name);
|
|
4018
|
+
if (idx >= 0) {
|
|
4019
|
+
entries[idx] = entry;
|
|
4020
|
+
} else {
|
|
4021
|
+
entries.push(entry);
|
|
4022
|
+
}
|
|
4023
|
+
await writeIndex(instanceUrl, token, scope, id, indexSlug, entries);
|
|
4024
|
+
}
|
|
4025
|
+
async function removeIndexEntry(instanceUrl, token, scope, id, indexSlug, name) {
|
|
4026
|
+
const entries = await readIndex(instanceUrl, token, scope, id, indexSlug);
|
|
4027
|
+
const filtered = entries.filter((e) => e.name !== name);
|
|
4028
|
+
if (filtered.length !== entries.length) {
|
|
4029
|
+
await writeIndex(instanceUrl, token, scope, id, indexSlug, filtered);
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
function extractSkillNames(pages, prefix) {
|
|
4033
|
+
const skillSuffix = "/SKILL";
|
|
4034
|
+
const names = /* @__PURE__ */ new Set();
|
|
4035
|
+
for (const p of pages) {
|
|
4036
|
+
if (p.slug.startsWith(prefix + "/") && p.slug.endsWith(skillSuffix)) {
|
|
4037
|
+
const middle = p.slug.slice(prefix.length + 1, -skillSuffix.length);
|
|
4038
|
+
if (middle && !middle.includes("/")) {
|
|
4039
|
+
names.add(middle);
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
return [...names];
|
|
4044
|
+
}
|
|
4045
|
+
async function rebuildIndex(instanceUrl, token, scope, id, prefix, indexSlug) {
|
|
4046
|
+
const pages = await listWikiPages(instanceUrl, token, scope, id);
|
|
4047
|
+
const actualNames = extractSkillNames(pages, prefix);
|
|
4048
|
+
const currentEntries = await readIndex(instanceUrl, token, scope, id, indexSlug);
|
|
4049
|
+
const indexed = new Set(currentEntries.map((e) => e.name));
|
|
4050
|
+
const actual = new Set(actualNames);
|
|
4051
|
+
let dirty = false;
|
|
4052
|
+
const removed = currentEntries.filter((e) => !actual.has(e.name));
|
|
4053
|
+
if (removed.length > 0) dirty = true;
|
|
4054
|
+
const added = [];
|
|
4055
|
+
for (const name of actualNames) {
|
|
4056
|
+
if (!indexed.has(name)) {
|
|
4057
|
+
added.push(name);
|
|
4058
|
+
dirty = true;
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
if (!dirty && added.length === 0) return currentEntries;
|
|
4062
|
+
const kept = currentEntries.filter((e) => actual.has(e.name));
|
|
4063
|
+
for (const name of added) {
|
|
4064
|
+
kept.push({
|
|
4065
|
+
name,
|
|
4066
|
+
description: "(auto-indexed \u2014 update description with gitlab_skill_save)",
|
|
4067
|
+
draft: prefix === DRAFTS_PREFIX
|
|
4068
|
+
});
|
|
4069
|
+
}
|
|
4070
|
+
await writeIndex(instanceUrl, token, scope, id, indexSlug, kept);
|
|
4071
|
+
return kept;
|
|
4072
|
+
}
|
|
4073
|
+
async function upsertPage(instanceUrl, token, scope, id, slug, content) {
|
|
4074
|
+
try {
|
|
4075
|
+
await updateWikiPage(instanceUrl, token, scope, id, slug, content);
|
|
4076
|
+
} catch {
|
|
4077
|
+
await createWikiPage(instanceUrl, token, scope, id, slug, content);
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
3963
4080
|
function makeSkillTools(ctx) {
|
|
3964
4081
|
function authAndValidate(projectId) {
|
|
3965
4082
|
const auth = ctx.ensureAuth();
|
|
@@ -3970,11 +4087,9 @@ function makeSkillTools(ctx) {
|
|
|
3970
4087
|
}
|
|
3971
4088
|
return {
|
|
3972
4089
|
gitlab_skill_list: (0, import_plugin6.tool)({
|
|
3973
|
-
description: "List available project skills and optionally draft skills.\nSkills define step-by-step procedures for common tasks (e.g., incident retros, debugging, deployments).",
|
|
4090
|
+
description: "List available project skills and optionally draft skills.\nSkills define step-by-step procedures for common tasks (e.g., incident retros, debugging, deployments).\nAuto-rebuilds the skill index if it is out of sync with actual skill pages.",
|
|
3974
4091
|
args: {
|
|
3975
|
-
project_id: z6.string().describe(
|
|
3976
|
-
'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
|
|
3977
|
-
),
|
|
4092
|
+
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
3978
4093
|
include_drafts: z6.boolean().optional().describe("Also list draft skills (default: false)"),
|
|
3979
4094
|
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
3980
4095
|
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
@@ -3983,23 +4098,27 @@ function makeSkillTools(ctx) {
|
|
|
3983
4098
|
const auth = authAndValidate(args.project_id);
|
|
3984
4099
|
const { scope, id } = resolveScope2(args);
|
|
3985
4100
|
try {
|
|
3986
|
-
const
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
4101
|
+
const published = await rebuildIndex(
|
|
4102
|
+
auth.instanceUrl,
|
|
4103
|
+
auth.token,
|
|
4104
|
+
scope,
|
|
4105
|
+
id,
|
|
4106
|
+
SKILLS_PREFIX,
|
|
4107
|
+
SKILLS_INDEX
|
|
4108
|
+
);
|
|
3993
4109
|
let drafts = [];
|
|
3994
4110
|
if (args.include_drafts) {
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4111
|
+
drafts = await rebuildIndex(
|
|
4112
|
+
auth.instanceUrl,
|
|
4113
|
+
auth.token,
|
|
4114
|
+
scope,
|
|
4115
|
+
id,
|
|
4116
|
+
DRAFTS_PREFIX,
|
|
4117
|
+
DRAFTS_INDEX
|
|
4118
|
+
);
|
|
4119
|
+
drafts = drafts.map((e) => ({ ...e, draft: true }));
|
|
4001
4120
|
}
|
|
4002
|
-
const all = [...
|
|
4121
|
+
const all = [...published, ...drafts];
|
|
4003
4122
|
if (all.length === 0) return "No skills found. Use gitlab_skill_save to create one.";
|
|
4004
4123
|
return JSON.stringify(all, null, 2);
|
|
4005
4124
|
} catch (err) {
|
|
@@ -4008,11 +4127,9 @@ function makeSkillTools(ctx) {
|
|
|
4008
4127
|
}
|
|
4009
4128
|
}),
|
|
4010
4129
|
gitlab_skill_load: (0, import_plugin6.tool)({
|
|
4011
|
-
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.",
|
|
4130
|
+
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.\nReturns the SKILL content and lists available reference pages.",
|
|
4012
4131
|
args: {
|
|
4013
|
-
project_id: z6.string().describe(
|
|
4014
|
-
'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
|
|
4015
|
-
),
|
|
4132
|
+
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4016
4133
|
name: z6.string().describe('Skill name (e.g., "incident-retro", "helm-rollback")'),
|
|
4017
4134
|
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
4018
4135
|
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
@@ -4020,41 +4137,46 @@ function makeSkillTools(ctx) {
|
|
|
4020
4137
|
execute: async (args) => {
|
|
4021
4138
|
const auth = authAndValidate(args.project_id);
|
|
4022
4139
|
const { scope, id } = resolveScope2(args);
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
auth.instanceUrl,
|
|
4026
|
-
auth.token,
|
|
4027
|
-
scope,
|
|
4028
|
-
id,
|
|
4029
|
-
`${SKILLS_PREFIX}/${args.name}`
|
|
4030
|
-
);
|
|
4031
|
-
return page.content;
|
|
4032
|
-
} catch {
|
|
4140
|
+
const prefixes = [SKILLS_PREFIX, DRAFTS_PREFIX];
|
|
4141
|
+
for (const prefix of prefixes) {
|
|
4033
4142
|
try {
|
|
4034
|
-
const
|
|
4143
|
+
const page = await getWikiPage(
|
|
4035
4144
|
auth.instanceUrl,
|
|
4036
4145
|
auth.token,
|
|
4037
4146
|
scope,
|
|
4038
4147
|
id,
|
|
4039
|
-
`${
|
|
4148
|
+
`${prefix}/${args.name}/SKILL`
|
|
4040
4149
|
);
|
|
4041
|
-
|
|
4150
|
+
const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id);
|
|
4151
|
+
const refPrefix = `${prefix}/${args.name}/references/`;
|
|
4152
|
+
const refs = pages.filter((p) => p.slug.startsWith(refPrefix)).map((p) => p.slug.slice(refPrefix.length));
|
|
4153
|
+
const isDraft = prefix === DRAFTS_PREFIX;
|
|
4154
|
+
let result = isDraft ? `[DRAFT SKILL]
|
|
4155
|
+
|
|
4156
|
+
${page.content}` : page.content;
|
|
4157
|
+
if (refs.length > 0) {
|
|
4158
|
+
result += `
|
|
4042
4159
|
|
|
4043
|
-
|
|
4160
|
+
---
|
|
4161
|
+
Available references: ${refs.join(", ")}`;
|
|
4162
|
+
result += `
|
|
4163
|
+
Load with: gitlab_skill_load_reference(name="${args.name}", reference="<name>")`;
|
|
4164
|
+
}
|
|
4165
|
+
return result;
|
|
4044
4166
|
} catch {
|
|
4045
|
-
|
|
4167
|
+
continue;
|
|
4046
4168
|
}
|
|
4047
4169
|
}
|
|
4170
|
+
return `Skill "${args.name}" not found. Use gitlab_skill_list to see available skills.`;
|
|
4048
4171
|
}
|
|
4049
4172
|
}),
|
|
4050
4173
|
gitlab_skill_save: (0, import_plugin6.tool)({
|
|
4051
|
-
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.",
|
|
4174
|
+
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.\nUpdates the skill index with the provided description.",
|
|
4052
4175
|
args: {
|
|
4053
|
-
project_id: z6.string().describe(
|
|
4054
|
-
'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
|
|
4055
|
-
),
|
|
4176
|
+
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4056
4177
|
name: z6.string().describe('Skill name (e.g., "incident-retro")'),
|
|
4057
4178
|
content: z6.string().describe("Skill content in markdown"),
|
|
4179
|
+
description: z6.string().describe("Short description for the skill index (1-2 sentences)"),
|
|
4058
4180
|
draft: z6.boolean().optional().describe("Save as draft skill (default: false)"),
|
|
4059
4181
|
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
4060
4182
|
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
@@ -4063,35 +4185,27 @@ ${draft.content}`;
|
|
|
4063
4185
|
const auth = authAndValidate(args.project_id);
|
|
4064
4186
|
const { scope, id } = resolveScope2(args);
|
|
4065
4187
|
const prefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
|
|
4066
|
-
const
|
|
4188
|
+
const indexSlug = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
|
|
4189
|
+
const slug = `${prefix}/${args.name}/SKILL`;
|
|
4067
4190
|
const label = args.draft ? "draft " : "";
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
await new Promise((r) => setTimeout(r, 1e3 * (attempt + 1)));
|
|
4080
|
-
continue;
|
|
4081
|
-
}
|
|
4082
|
-
return `Error saving skill: ${msg}`;
|
|
4083
|
-
}
|
|
4084
|
-
}
|
|
4191
|
+
try {
|
|
4192
|
+
await upsertPage(auth.instanceUrl, auth.token, scope, id, slug, args.content);
|
|
4193
|
+
await upsertIndexEntry(auth.instanceUrl, auth.token, scope, id, indexSlug, {
|
|
4194
|
+
name: args.name,
|
|
4195
|
+
description: args.description,
|
|
4196
|
+
source: "project",
|
|
4197
|
+
draft: !!args.draft
|
|
4198
|
+
});
|
|
4199
|
+
return `Saved ${label}skill: ${args.name}`;
|
|
4200
|
+
} catch (err) {
|
|
4201
|
+
return `Error saving skill: ${err.message}`;
|
|
4085
4202
|
}
|
|
4086
|
-
return `Error saving skill: failed after 3 retries`;
|
|
4087
4203
|
}
|
|
4088
4204
|
}),
|
|
4089
4205
|
gitlab_skill_promote: (0, import_plugin6.tool)({
|
|
4090
|
-
description: "Promote a draft skill to published.\nMoves
|
|
4206
|
+
description: "Promote a draft skill to published.\nMoves all skill pages from drafts to published and updates both indexes.",
|
|
4091
4207
|
args: {
|
|
4092
|
-
project_id: z6.string().describe(
|
|
4093
|
-
'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
|
|
4094
|
-
),
|
|
4208
|
+
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4095
4209
|
name: z6.string().describe("Skill name to promote"),
|
|
4096
4210
|
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
4097
4211
|
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
@@ -4099,36 +4213,170 @@ ${draft.content}`;
|
|
|
4099
4213
|
execute: async (args) => {
|
|
4100
4214
|
const auth = authAndValidate(args.project_id);
|
|
4101
4215
|
const { scope, id } = resolveScope2(args);
|
|
4102
|
-
const draftSlug = `${DRAFTS_PREFIX}/${args.name}`;
|
|
4103
|
-
const publishedSlug = `${SKILLS_PREFIX}/${args.name}`;
|
|
4104
4216
|
try {
|
|
4105
|
-
const
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4217
|
+
const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id, true);
|
|
4218
|
+
const draftPrefix = `${DRAFTS_PREFIX}/${args.name}/`;
|
|
4219
|
+
const draftPages = pages.filter(
|
|
4220
|
+
(p) => p.slug.startsWith(draftPrefix) && p.content
|
|
4221
|
+
);
|
|
4222
|
+
if (draftPages.length === 0) {
|
|
4223
|
+
return `Draft skill "${args.name}" not found. Use gitlab_skill_list(include_drafts=true) to see available drafts.`;
|
|
4224
|
+
}
|
|
4225
|
+
const draftIndex = await readIndex(auth.instanceUrl, auth.token, scope, id, DRAFTS_INDEX);
|
|
4226
|
+
const entry = draftIndex.find((e) => e.name === args.name);
|
|
4227
|
+
const description = entry?.description ?? "(promoted from draft)";
|
|
4228
|
+
for (const page of draftPages) {
|
|
4229
|
+
const newSlug = page.slug.replace(DRAFTS_PREFIX, SKILLS_PREFIX);
|
|
4230
|
+
await upsertPage(auth.instanceUrl, auth.token, scope, id, newSlug, page.content);
|
|
4231
|
+
await deleteWikiPage(auth.instanceUrl, auth.token, scope, id, page.slug);
|
|
4232
|
+
}
|
|
4233
|
+
await removeIndexEntry(auth.instanceUrl, auth.token, scope, id, DRAFTS_INDEX, args.name);
|
|
4234
|
+
await upsertIndexEntry(auth.instanceUrl, auth.token, scope, id, SKILLS_INDEX, {
|
|
4235
|
+
name: args.name,
|
|
4236
|
+
description,
|
|
4237
|
+
source: "project",
|
|
4238
|
+
draft: false
|
|
4239
|
+
});
|
|
4240
|
+
return `Promoted skill "${args.name}" from draft to published.`;
|
|
4241
|
+
} catch (err) {
|
|
4242
|
+
return `Error promoting skill: ${err.message}`;
|
|
4243
|
+
}
|
|
4244
|
+
}
|
|
4245
|
+
}),
|
|
4246
|
+
gitlab_skill_discover: (0, import_plugin6.tool)({
|
|
4247
|
+
description: "Search for skills available in the group wiki.\nReads the group-level skill index and filters by query.\nUse gitlab_skill_install to copy a discovered skill to your project.",
|
|
4248
|
+
args: {
|
|
4249
|
+
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4250
|
+
query: z6.string().describe("Search query (matches skill name and description)"),
|
|
4251
|
+
group_id: z6.string().describe("Group path to search for shared skills")
|
|
4252
|
+
},
|
|
4253
|
+
execute: async (args) => {
|
|
4254
|
+
const auth = authAndValidate(args.project_id);
|
|
4255
|
+
try {
|
|
4256
|
+
const entries = await readIndex(
|
|
4257
|
+
auth.instanceUrl,
|
|
4258
|
+
auth.token,
|
|
4259
|
+
"groups",
|
|
4260
|
+
args.group_id,
|
|
4261
|
+
SKILLS_INDEX
|
|
4262
|
+
);
|
|
4263
|
+
if (entries.length === 0) {
|
|
4264
|
+
return `No skills found in group "${args.group_id}" wiki. The group skill index (${SKILLS_INDEX}) is empty or does not exist.`;
|
|
4265
|
+
}
|
|
4266
|
+
const q = args.query.toLowerCase();
|
|
4267
|
+
const matches = entries.filter(
|
|
4268
|
+
(e) => e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q)
|
|
4269
|
+
);
|
|
4270
|
+
if (matches.length === 0) {
|
|
4271
|
+
return `No skills matching "${args.query}" in group "${args.group_id}". Available skills:
|
|
4272
|
+
${entries.map((e) => `- ${e.name}: ${e.description}`).join("\n")}`;
|
|
4273
|
+
}
|
|
4274
|
+
return `Found ${matches.length} skill(s) in group "${args.group_id}":
|
|
4275
|
+
|
|
4276
|
+
` + matches.map(
|
|
4277
|
+
(e) => `**${e.name}**: ${e.description}
|
|
4278
|
+
Install: gitlab_skill_install(name="${e.name}", group_id="${args.group_id}")`
|
|
4279
|
+
).join("\n\n");
|
|
4280
|
+
} catch (err) {
|
|
4281
|
+
return `Error discovering skills: ${err.message}`;
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
}),
|
|
4285
|
+
gitlab_skill_install: (0, import_plugin6.tool)({
|
|
4286
|
+
description: "Install a skill from a group wiki into the project wiki.\nCopies all skill pages (SKILL + references) from the group to the project.\nUpdates the project skill index with the installed skill.",
|
|
4287
|
+
args: {
|
|
4288
|
+
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4289
|
+
name: z6.string().describe('Skill name to install (e.g., "incident-retro")'),
|
|
4290
|
+
group_id: z6.string().describe("Group path to install from"),
|
|
4291
|
+
draft: z6.boolean().optional().describe("Install as draft (default: false)")
|
|
4292
|
+
},
|
|
4293
|
+
execute: async (args) => {
|
|
4294
|
+
const auth = authAndValidate(args.project_id);
|
|
4295
|
+
const projectScope = resolveScope2(args);
|
|
4296
|
+
try {
|
|
4297
|
+
const groupPages = await listWikiPages(
|
|
4298
|
+
auth.instanceUrl,
|
|
4299
|
+
auth.token,
|
|
4300
|
+
"groups",
|
|
4301
|
+
args.group_id,
|
|
4302
|
+
true
|
|
4303
|
+
);
|
|
4304
|
+
const sourcePrefix = `${SKILLS_PREFIX}/${args.name}/`;
|
|
4305
|
+
const skillPages = groupPages.filter(
|
|
4306
|
+
(p) => p.slug.startsWith(sourcePrefix) && p.content
|
|
4307
|
+
);
|
|
4308
|
+
if (skillPages.length === 0) {
|
|
4309
|
+
return `Skill "${args.name}" not found in group "${args.group_id}" wiki.`;
|
|
4310
|
+
}
|
|
4311
|
+
const targetPrefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
|
|
4312
|
+
const targetIndex = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
|
|
4313
|
+
for (const page of skillPages) {
|
|
4314
|
+
const newSlug = page.slug.replace(SKILLS_PREFIX, targetPrefix);
|
|
4315
|
+
await upsertPage(
|
|
4117
4316
|
auth.instanceUrl,
|
|
4118
4317
|
auth.token,
|
|
4119
|
-
scope,
|
|
4120
|
-
id,
|
|
4121
|
-
|
|
4122
|
-
|
|
4318
|
+
projectScope.scope,
|
|
4319
|
+
projectScope.id,
|
|
4320
|
+
newSlug,
|
|
4321
|
+
page.content
|
|
4123
4322
|
);
|
|
4124
4323
|
}
|
|
4125
|
-
|
|
4126
|
-
|
|
4324
|
+
const groupIndex = await readIndex(
|
|
4325
|
+
auth.instanceUrl,
|
|
4326
|
+
auth.token,
|
|
4327
|
+
"groups",
|
|
4328
|
+
args.group_id,
|
|
4329
|
+
SKILLS_INDEX
|
|
4330
|
+
);
|
|
4331
|
+
const entry = groupIndex.find((e) => e.name === args.name);
|
|
4332
|
+
const description = entry?.description ?? "(installed from group)";
|
|
4333
|
+
await upsertIndexEntry(
|
|
4334
|
+
auth.instanceUrl,
|
|
4335
|
+
auth.token,
|
|
4336
|
+
projectScope.scope,
|
|
4337
|
+
projectScope.id,
|
|
4338
|
+
targetIndex,
|
|
4339
|
+
{
|
|
4340
|
+
name: args.name,
|
|
4341
|
+
description,
|
|
4342
|
+
source: `group:${args.group_id}`,
|
|
4343
|
+
draft: !!args.draft
|
|
4344
|
+
}
|
|
4345
|
+
);
|
|
4346
|
+
return `Installed skill "${args.name}" from group "${args.group_id}" into project. ${skillPages.length} page(s) copied.`;
|
|
4127
4347
|
} catch (err) {
|
|
4128
|
-
|
|
4129
|
-
|
|
4348
|
+
return `Error installing skill: ${err.message}`;
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
}),
|
|
4352
|
+
gitlab_skill_delete: (0, import_plugin6.tool)({
|
|
4353
|
+
description: "Delete a skill and remove it from the index.\nRemoves all pages under the skill directory (SKILL + references).\nWiki git history preserves deleted content.",
|
|
4354
|
+
args: {
|
|
4355
|
+
project_id: z6.string().describe(PROJECT_ID_DESC2),
|
|
4356
|
+
name: z6.string().describe("Skill name to delete"),
|
|
4357
|
+
draft: z6.boolean().optional().describe("Delete from drafts instead of published (default: false)"),
|
|
4358
|
+
scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
|
|
4359
|
+
group_id: z6.string().optional().describe("Group path (required when scope is groups)")
|
|
4360
|
+
},
|
|
4361
|
+
execute: async (args) => {
|
|
4362
|
+
const auth = authAndValidate(args.project_id);
|
|
4363
|
+
const { scope, id } = resolveScope2(args);
|
|
4364
|
+
const prefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
|
|
4365
|
+
const indexSlug = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
|
|
4366
|
+
const skillPrefix = `${prefix}/${args.name}/`;
|
|
4367
|
+
try {
|
|
4368
|
+
const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id);
|
|
4369
|
+
const skillPages = pages.filter((p) => p.slug.startsWith(skillPrefix));
|
|
4370
|
+
if (skillPages.length === 0) {
|
|
4371
|
+
return `Skill "${args.name}" not found. Use gitlab_skill_list to see available skills.`;
|
|
4130
4372
|
}
|
|
4131
|
-
|
|
4373
|
+
for (const page of skillPages) {
|
|
4374
|
+
await deleteWikiPage(auth.instanceUrl, auth.token, scope, id, page.slug);
|
|
4375
|
+
}
|
|
4376
|
+
await removeIndexEntry(auth.instanceUrl, auth.token, scope, id, indexSlug, args.name);
|
|
4377
|
+
return `Deleted skill "${args.name}" (${skillPages.length} page(s) removed).`;
|
|
4378
|
+
} catch (err) {
|
|
4379
|
+
return `Error deleting skill: ${err.message}`;
|
|
4132
4380
|
}
|
|
4133
4381
|
}
|
|
4134
4382
|
})
|