opencode-gitlab-dap 1.12.1 → 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/dist/index.js CHANGED
@@ -3782,6 +3782,9 @@ var z6 = tool6.schema;
3782
3782
  var PREFIX2 = "agents";
3783
3783
  var SKILLS_PREFIX = `${PREFIX2}/skills`;
3784
3784
  var DRAFTS_PREFIX = `${PREFIX2}/skills-drafts`;
3785
+ var SKILLS_INDEX = `${SKILLS_PREFIX}/index`;
3786
+ var DRAFTS_INDEX = `${DRAFTS_PREFIX}/index`;
3787
+ var PROJECT_ID_DESC2 = 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.';
3785
3788
  function resolveScope2(args) {
3786
3789
  if (args.scope === "groups" && args.group_id) {
3787
3790
  return { scope: "groups", id: args.group_id };
@@ -3794,6 +3797,117 @@ function validateProjectId2(projectId) {
3794
3797
  }
3795
3798
  return null;
3796
3799
  }
3800
+ function parseIndex(content) {
3801
+ const entries = [];
3802
+ const blocks = content.split(/^## /m).filter(Boolean);
3803
+ for (const block of blocks) {
3804
+ const lines = block.trim().split("\n");
3805
+ const name = lines[0].trim();
3806
+ if (!name) continue;
3807
+ const rest = lines.slice(1).join("\n").trim();
3808
+ const descLines = [];
3809
+ let source;
3810
+ for (const line of rest.split("\n")) {
3811
+ if (line.startsWith("Source:")) {
3812
+ source = line.slice(7).trim();
3813
+ } else if (line.trim()) {
3814
+ descLines.push(line);
3815
+ }
3816
+ }
3817
+ entries.push({ name, description: descLines.join("\n"), source, draft: false });
3818
+ }
3819
+ return entries;
3820
+ }
3821
+ function formatIndex(entries) {
3822
+ return entries.map((e) => {
3823
+ let block = `## ${e.name}
3824
+ ${e.description}`;
3825
+ if (e.source) block += `
3826
+ Source: ${e.source}`;
3827
+ return block;
3828
+ }).join("\n\n");
3829
+ }
3830
+ async function readIndex(instanceUrl, token, scope, id, indexSlug) {
3831
+ try {
3832
+ const page = await getWikiPage(instanceUrl, token, scope, id, indexSlug);
3833
+ return parseIndex(page.content);
3834
+ } catch {
3835
+ return [];
3836
+ }
3837
+ }
3838
+ async function writeIndex(instanceUrl, token, scope, id, indexSlug, entries) {
3839
+ const content = formatIndex(entries);
3840
+ try {
3841
+ await updateWikiPage(instanceUrl, token, scope, id, indexSlug, content);
3842
+ } catch {
3843
+ await createWikiPage(instanceUrl, token, scope, id, indexSlug, content || "# Skills Index");
3844
+ }
3845
+ }
3846
+ async function upsertIndexEntry(instanceUrl, token, scope, id, indexSlug, entry) {
3847
+ const entries = await readIndex(instanceUrl, token, scope, id, indexSlug);
3848
+ const idx = entries.findIndex((e) => e.name === entry.name);
3849
+ if (idx >= 0) {
3850
+ entries[idx] = entry;
3851
+ } else {
3852
+ entries.push(entry);
3853
+ }
3854
+ await writeIndex(instanceUrl, token, scope, id, indexSlug, entries);
3855
+ }
3856
+ async function removeIndexEntry(instanceUrl, token, scope, id, indexSlug, name) {
3857
+ const entries = await readIndex(instanceUrl, token, scope, id, indexSlug);
3858
+ const filtered = entries.filter((e) => e.name !== name);
3859
+ if (filtered.length !== entries.length) {
3860
+ await writeIndex(instanceUrl, token, scope, id, indexSlug, filtered);
3861
+ }
3862
+ }
3863
+ function extractSkillNames(pages, prefix) {
3864
+ const skillSuffix = "/SKILL";
3865
+ const names = /* @__PURE__ */ new Set();
3866
+ for (const p of pages) {
3867
+ if (p.slug.startsWith(prefix + "/") && p.slug.endsWith(skillSuffix)) {
3868
+ const middle = p.slug.slice(prefix.length + 1, -skillSuffix.length);
3869
+ if (middle && !middle.includes("/")) {
3870
+ names.add(middle);
3871
+ }
3872
+ }
3873
+ }
3874
+ return [...names];
3875
+ }
3876
+ async function rebuildIndex(instanceUrl, token, scope, id, prefix, indexSlug) {
3877
+ const pages = await listWikiPages(instanceUrl, token, scope, id);
3878
+ const actualNames = extractSkillNames(pages, prefix);
3879
+ const currentEntries = await readIndex(instanceUrl, token, scope, id, indexSlug);
3880
+ const indexed = new Set(currentEntries.map((e) => e.name));
3881
+ const actual = new Set(actualNames);
3882
+ let dirty = false;
3883
+ const removed = currentEntries.filter((e) => !actual.has(e.name));
3884
+ if (removed.length > 0) dirty = true;
3885
+ const added = [];
3886
+ for (const name of actualNames) {
3887
+ if (!indexed.has(name)) {
3888
+ added.push(name);
3889
+ dirty = true;
3890
+ }
3891
+ }
3892
+ if (!dirty && added.length === 0) return currentEntries;
3893
+ const kept = currentEntries.filter((e) => actual.has(e.name));
3894
+ for (const name of added) {
3895
+ kept.push({
3896
+ name,
3897
+ description: "(auto-indexed \u2014 update description with gitlab_skill_save)",
3898
+ draft: prefix === DRAFTS_PREFIX
3899
+ });
3900
+ }
3901
+ await writeIndex(instanceUrl, token, scope, id, indexSlug, kept);
3902
+ return kept;
3903
+ }
3904
+ async function upsertPage(instanceUrl, token, scope, id, slug, content) {
3905
+ try {
3906
+ await updateWikiPage(instanceUrl, token, scope, id, slug, content);
3907
+ } catch {
3908
+ await createWikiPage(instanceUrl, token, scope, id, slug, content);
3909
+ }
3910
+ }
3797
3911
  function makeSkillTools(ctx) {
3798
3912
  function authAndValidate(projectId) {
3799
3913
  const auth = ctx.ensureAuth();
@@ -3804,11 +3918,9 @@ function makeSkillTools(ctx) {
3804
3918
  }
3805
3919
  return {
3806
3920
  gitlab_skill_list: tool6({
3807
- description: "List available project skills and optionally draft skills.\nSkills define step-by-step procedures for common tasks (e.g., incident retros, debugging, deployments).",
3921
+ 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.",
3808
3922
  args: {
3809
- project_id: z6.string().describe(
3810
- 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3811
- ),
3923
+ project_id: z6.string().describe(PROJECT_ID_DESC2),
3812
3924
  include_drafts: z6.boolean().optional().describe("Also list draft skills (default: false)"),
3813
3925
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
3814
3926
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
@@ -3817,23 +3929,27 @@ function makeSkillTools(ctx) {
3817
3929
  const auth = authAndValidate(args.project_id);
3818
3930
  const { scope, id } = resolveScope2(args);
3819
3931
  try {
3820
- const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id);
3821
- const indexSlug = `${SKILLS_PREFIX}/index`;
3822
- const skills = pages.filter((p) => p.slug.startsWith(`${SKILLS_PREFIX}/`) && p.slug !== indexSlug).map((p) => ({
3823
- name: p.slug.slice(SKILLS_PREFIX.length + 1),
3824
- title: p.title,
3825
- draft: false
3826
- }));
3932
+ const published = await rebuildIndex(
3933
+ auth.instanceUrl,
3934
+ auth.token,
3935
+ scope,
3936
+ id,
3937
+ SKILLS_PREFIX,
3938
+ SKILLS_INDEX
3939
+ );
3827
3940
  let drafts = [];
3828
3941
  if (args.include_drafts) {
3829
- const draftsIndexSlug = `${DRAFTS_PREFIX}/index`;
3830
- drafts = pages.filter((p) => p.slug.startsWith(`${DRAFTS_PREFIX}/`) && p.slug !== draftsIndexSlug).map((p) => ({
3831
- name: p.slug.slice(DRAFTS_PREFIX.length + 1),
3832
- title: p.title,
3833
- draft: true
3834
- }));
3942
+ drafts = await rebuildIndex(
3943
+ auth.instanceUrl,
3944
+ auth.token,
3945
+ scope,
3946
+ id,
3947
+ DRAFTS_PREFIX,
3948
+ DRAFTS_INDEX
3949
+ );
3950
+ drafts = drafts.map((e) => ({ ...e, draft: true }));
3835
3951
  }
3836
- const all = [...skills, ...drafts];
3952
+ const all = [...published, ...drafts];
3837
3953
  if (all.length === 0) return "No skills found. Use gitlab_skill_save to create one.";
3838
3954
  return JSON.stringify(all, null, 2);
3839
3955
  } catch (err) {
@@ -3842,11 +3958,9 @@ function makeSkillTools(ctx) {
3842
3958
  }
3843
3959
  }),
3844
3960
  gitlab_skill_load: tool6({
3845
- 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.",
3961
+ 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.",
3846
3962
  args: {
3847
- project_id: z6.string().describe(
3848
- 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3849
- ),
3963
+ project_id: z6.string().describe(PROJECT_ID_DESC2),
3850
3964
  name: z6.string().describe('Skill name (e.g., "incident-retro", "helm-rollback")'),
3851
3965
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
3852
3966
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
@@ -3854,41 +3968,46 @@ function makeSkillTools(ctx) {
3854
3968
  execute: async (args) => {
3855
3969
  const auth = authAndValidate(args.project_id);
3856
3970
  const { scope, id } = resolveScope2(args);
3857
- try {
3858
- const page = await getWikiPage(
3859
- auth.instanceUrl,
3860
- auth.token,
3861
- scope,
3862
- id,
3863
- `${SKILLS_PREFIX}/${args.name}`
3864
- );
3865
- return page.content;
3866
- } catch {
3971
+ const prefixes = [SKILLS_PREFIX, DRAFTS_PREFIX];
3972
+ for (const prefix of prefixes) {
3867
3973
  try {
3868
- const draft = await getWikiPage(
3974
+ const page = await getWikiPage(
3869
3975
  auth.instanceUrl,
3870
3976
  auth.token,
3871
3977
  scope,
3872
3978
  id,
3873
- `${DRAFTS_PREFIX}/${args.name}`
3979
+ `${prefix}/${args.name}/SKILL`
3874
3980
  );
3875
- return `[DRAFT SKILL]
3981
+ const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id);
3982
+ const refPrefix = `${prefix}/${args.name}/references/`;
3983
+ const refs = pages.filter((p) => p.slug.startsWith(refPrefix)).map((p) => p.slug.slice(refPrefix.length));
3984
+ const isDraft = prefix === DRAFTS_PREFIX;
3985
+ let result = isDraft ? `[DRAFT SKILL]
3986
+
3987
+ ${page.content}` : page.content;
3988
+ if (refs.length > 0) {
3989
+ result += `
3876
3990
 
3877
- ${draft.content}`;
3991
+ ---
3992
+ Available references: ${refs.join(", ")}`;
3993
+ result += `
3994
+ Load with: gitlab_skill_load_reference(name="${args.name}", reference="<name>")`;
3995
+ }
3996
+ return result;
3878
3997
  } catch {
3879
- return `Skill "${args.name}" not found. Use gitlab_skill_list to see available skills.`;
3998
+ continue;
3880
3999
  }
3881
4000
  }
4001
+ return `Skill "${args.name}" not found. Use gitlab_skill_list to see available skills.`;
3882
4002
  }
3883
4003
  }),
3884
4004
  gitlab_skill_save: tool6({
3885
- 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.",
4005
+ 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.",
3886
4006
  args: {
3887
- project_id: z6.string().describe(
3888
- 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3889
- ),
4007
+ project_id: z6.string().describe(PROJECT_ID_DESC2),
3890
4008
  name: z6.string().describe('Skill name (e.g., "incident-retro")'),
3891
4009
  content: z6.string().describe("Skill content in markdown"),
4010
+ description: z6.string().describe("Short description for the skill index (1-2 sentences)"),
3892
4011
  draft: z6.boolean().optional().describe("Save as draft skill (default: false)"),
3893
4012
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
3894
4013
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
@@ -3897,35 +4016,27 @@ ${draft.content}`;
3897
4016
  const auth = authAndValidate(args.project_id);
3898
4017
  const { scope, id } = resolveScope2(args);
3899
4018
  const prefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
3900
- const slug = `${prefix}/${args.name}`;
4019
+ const indexSlug = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
4020
+ const slug = `${prefix}/${args.name}/SKILL`;
3901
4021
  const label = args.draft ? "draft " : "";
3902
- for (let attempt = 0; attempt < 3; attempt++) {
3903
- try {
3904
- await updateWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.content);
3905
- return `Updated ${label}skill: ${args.name}`;
3906
- } catch {
3907
- try {
3908
- await createWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.content);
3909
- return `Created ${label}skill: ${args.name}`;
3910
- } catch (err) {
3911
- const msg = err.message ?? "";
3912
- if (msg.includes("Duplicate page") || msg.includes("reference update")) {
3913
- await new Promise((r) => setTimeout(r, 1e3 * (attempt + 1)));
3914
- continue;
3915
- }
3916
- return `Error saving skill: ${msg}`;
3917
- }
3918
- }
4022
+ try {
4023
+ await upsertPage(auth.instanceUrl, auth.token, scope, id, slug, args.content);
4024
+ await upsertIndexEntry(auth.instanceUrl, auth.token, scope, id, indexSlug, {
4025
+ name: args.name,
4026
+ description: args.description,
4027
+ source: "project",
4028
+ draft: !!args.draft
4029
+ });
4030
+ return `Saved ${label}skill: ${args.name}`;
4031
+ } catch (err) {
4032
+ return `Error saving skill: ${err.message}`;
3919
4033
  }
3920
- return `Error saving skill: failed after 3 retries`;
3921
4034
  }
3922
4035
  }),
3923
4036
  gitlab_skill_promote: tool6({
3924
- description: "Promote a draft skill to published.\nMoves the skill from the drafts directory to the published skills directory.",
4037
+ description: "Promote a draft skill to published.\nMoves all skill pages from drafts to published and updates both indexes.",
3925
4038
  args: {
3926
- project_id: z6.string().describe(
3927
- 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3928
- ),
4039
+ project_id: z6.string().describe(PROJECT_ID_DESC2),
3929
4040
  name: z6.string().describe("Skill name to promote"),
3930
4041
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
3931
4042
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
@@ -3933,36 +4044,170 @@ ${draft.content}`;
3933
4044
  execute: async (args) => {
3934
4045
  const auth = authAndValidate(args.project_id);
3935
4046
  const { scope, id } = resolveScope2(args);
3936
- const draftSlug = `${DRAFTS_PREFIX}/${args.name}`;
3937
- const publishedSlug = `${SKILLS_PREFIX}/${args.name}`;
3938
4047
  try {
3939
- const draft = await getWikiPage(auth.instanceUrl, auth.token, scope, id, draftSlug);
3940
- try {
3941
- await updateWikiPage(
3942
- auth.instanceUrl,
3943
- auth.token,
3944
- scope,
3945
- id,
3946
- publishedSlug,
3947
- draft.content
3948
- );
3949
- } catch {
3950
- await createWikiPage(
4048
+ const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id, true);
4049
+ const draftPrefix = `${DRAFTS_PREFIX}/${args.name}/`;
4050
+ const draftPages = pages.filter(
4051
+ (p) => p.slug.startsWith(draftPrefix) && p.content
4052
+ );
4053
+ if (draftPages.length === 0) {
4054
+ return `Draft skill "${args.name}" not found. Use gitlab_skill_list(include_drafts=true) to see available drafts.`;
4055
+ }
4056
+ const draftIndex = await readIndex(auth.instanceUrl, auth.token, scope, id, DRAFTS_INDEX);
4057
+ const entry = draftIndex.find((e) => e.name === args.name);
4058
+ const description = entry?.description ?? "(promoted from draft)";
4059
+ for (const page of draftPages) {
4060
+ const newSlug = page.slug.replace(DRAFTS_PREFIX, SKILLS_PREFIX);
4061
+ await upsertPage(auth.instanceUrl, auth.token, scope, id, newSlug, page.content);
4062
+ await deleteWikiPage(auth.instanceUrl, auth.token, scope, id, page.slug);
4063
+ }
4064
+ await removeIndexEntry(auth.instanceUrl, auth.token, scope, id, DRAFTS_INDEX, args.name);
4065
+ await upsertIndexEntry(auth.instanceUrl, auth.token, scope, id, SKILLS_INDEX, {
4066
+ name: args.name,
4067
+ description,
4068
+ source: "project",
4069
+ draft: false
4070
+ });
4071
+ return `Promoted skill "${args.name}" from draft to published.`;
4072
+ } catch (err) {
4073
+ return `Error promoting skill: ${err.message}`;
4074
+ }
4075
+ }
4076
+ }),
4077
+ gitlab_skill_discover: tool6({
4078
+ 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.",
4079
+ args: {
4080
+ project_id: z6.string().describe(PROJECT_ID_DESC2),
4081
+ query: z6.string().describe("Search query (matches skill name and description)"),
4082
+ group_id: z6.string().describe("Group path to search for shared skills")
4083
+ },
4084
+ execute: async (args) => {
4085
+ const auth = authAndValidate(args.project_id);
4086
+ try {
4087
+ const entries = await readIndex(
4088
+ auth.instanceUrl,
4089
+ auth.token,
4090
+ "groups",
4091
+ args.group_id,
4092
+ SKILLS_INDEX
4093
+ );
4094
+ if (entries.length === 0) {
4095
+ return `No skills found in group "${args.group_id}" wiki. The group skill index (${SKILLS_INDEX}) is empty or does not exist.`;
4096
+ }
4097
+ const q = args.query.toLowerCase();
4098
+ const matches = entries.filter(
4099
+ (e) => e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q)
4100
+ );
4101
+ if (matches.length === 0) {
4102
+ return `No skills matching "${args.query}" in group "${args.group_id}". Available skills:
4103
+ ${entries.map((e) => `- ${e.name}: ${e.description}`).join("\n")}`;
4104
+ }
4105
+ return `Found ${matches.length} skill(s) in group "${args.group_id}":
4106
+
4107
+ ` + matches.map(
4108
+ (e) => `**${e.name}**: ${e.description}
4109
+ Install: gitlab_skill_install(name="${e.name}", group_id="${args.group_id}")`
4110
+ ).join("\n\n");
4111
+ } catch (err) {
4112
+ return `Error discovering skills: ${err.message}`;
4113
+ }
4114
+ }
4115
+ }),
4116
+ gitlab_skill_install: tool6({
4117
+ 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.",
4118
+ args: {
4119
+ project_id: z6.string().describe(PROJECT_ID_DESC2),
4120
+ name: z6.string().describe('Skill name to install (e.g., "incident-retro")'),
4121
+ group_id: z6.string().describe("Group path to install from"),
4122
+ draft: z6.boolean().optional().describe("Install as draft (default: false)")
4123
+ },
4124
+ execute: async (args) => {
4125
+ const auth = authAndValidate(args.project_id);
4126
+ const projectScope = resolveScope2(args);
4127
+ try {
4128
+ const groupPages = await listWikiPages(
4129
+ auth.instanceUrl,
4130
+ auth.token,
4131
+ "groups",
4132
+ args.group_id,
4133
+ true
4134
+ );
4135
+ const sourcePrefix = `${SKILLS_PREFIX}/${args.name}/`;
4136
+ const skillPages = groupPages.filter(
4137
+ (p) => p.slug.startsWith(sourcePrefix) && p.content
4138
+ );
4139
+ if (skillPages.length === 0) {
4140
+ return `Skill "${args.name}" not found in group "${args.group_id}" wiki.`;
4141
+ }
4142
+ const targetPrefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
4143
+ const targetIndex = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
4144
+ for (const page of skillPages) {
4145
+ const newSlug = page.slug.replace(SKILLS_PREFIX, targetPrefix);
4146
+ await upsertPage(
3951
4147
  auth.instanceUrl,
3952
4148
  auth.token,
3953
- scope,
3954
- id,
3955
- publishedSlug,
3956
- draft.content
4149
+ projectScope.scope,
4150
+ projectScope.id,
4151
+ newSlug,
4152
+ page.content
3957
4153
  );
3958
4154
  }
3959
- await deleteWikiPage(auth.instanceUrl, auth.token, scope, id, draftSlug);
3960
- return `Promoted skill "${args.name}" from draft to published.`;
4155
+ const groupIndex = await readIndex(
4156
+ auth.instanceUrl,
4157
+ auth.token,
4158
+ "groups",
4159
+ args.group_id,
4160
+ SKILLS_INDEX
4161
+ );
4162
+ const entry = groupIndex.find((e) => e.name === args.name);
4163
+ const description = entry?.description ?? "(installed from group)";
4164
+ await upsertIndexEntry(
4165
+ auth.instanceUrl,
4166
+ auth.token,
4167
+ projectScope.scope,
4168
+ projectScope.id,
4169
+ targetIndex,
4170
+ {
4171
+ name: args.name,
4172
+ description,
4173
+ source: `group:${args.group_id}`,
4174
+ draft: !!args.draft
4175
+ }
4176
+ );
4177
+ return `Installed skill "${args.name}" from group "${args.group_id}" into project. ${skillPages.length} page(s) copied.`;
3961
4178
  } catch (err) {
3962
- if (err.message?.includes("not found") || err.message?.includes("404")) {
3963
- return `Draft skill "${args.name}" not found. Use gitlab_skill_list(include_drafts=true) to see available drafts.`;
4179
+ return `Error installing skill: ${err.message}`;
4180
+ }
4181
+ }
4182
+ }),
4183
+ gitlab_skill_delete: tool6({
4184
+ 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.",
4185
+ args: {
4186
+ project_id: z6.string().describe(PROJECT_ID_DESC2),
4187
+ name: z6.string().describe("Skill name to delete"),
4188
+ draft: z6.boolean().optional().describe("Delete from drafts instead of published (default: false)"),
4189
+ scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
4190
+ group_id: z6.string().optional().describe("Group path (required when scope is groups)")
4191
+ },
4192
+ execute: async (args) => {
4193
+ const auth = authAndValidate(args.project_id);
4194
+ const { scope, id } = resolveScope2(args);
4195
+ const prefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
4196
+ const indexSlug = args.draft ? DRAFTS_INDEX : SKILLS_INDEX;
4197
+ const skillPrefix = `${prefix}/${args.name}/`;
4198
+ try {
4199
+ const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id);
4200
+ const skillPages = pages.filter((p) => p.slug.startsWith(skillPrefix));
4201
+ if (skillPages.length === 0) {
4202
+ return `Skill "${args.name}" not found. Use gitlab_skill_list to see available skills.`;
3964
4203
  }
3965
- return `Error promoting skill: ${err.message}`;
4204
+ for (const page of skillPages) {
4205
+ await deleteWikiPage(auth.instanceUrl, auth.token, scope, id, page.slug);
4206
+ }
4207
+ await removeIndexEntry(auth.instanceUrl, auth.token, scope, id, indexSlug, args.name);
4208
+ return `Deleted skill "${args.name}" (${skillPages.length} page(s) removed).`;
4209
+ } catch (err) {
4210
+ return `Error deleting skill: ${err.message}`;
3966
4211
  }
3967
4212
  }
3968
4213
  })