Skip to content

Commit dbb25c6

Browse files
brayanjulsclaude
andcommitted
fix: Preserve command namespace in Copilot skill names
Stop stripping namespace prefixes when converting commands to Copilot skills. `workflows:plan` now becomes `workflows-plan` instead of just `plan`, avoiding clashes with Copilot's own features in the chat UI. Also updates slash command references in body text to match: `/workflows:plan` → `/workflows-plan`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7055df5 commit dbb25c6

File tree

3 files changed

+68
-14
lines changed

3 files changed

+68
-14
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
date: 2026-02-17
3+
topic: copilot-skill-naming
4+
---
5+
6+
# Copilot Skill Naming: Preserve Namespace
7+
8+
## What We're Building
9+
10+
Change the Copilot converter to preserve command namespaces when converting commands to skills. Currently `workflows:plan` flattens to `plan`, which is too generic and clashes with Copilot's own features in the chat suggestion UI.
11+
12+
## Why This Approach
13+
14+
The `flattenCommandName` function strips everything before the last colon, producing names like `plan`, `review`, `work` that are too generic for Copilot's skill discovery UI. Replacing colons with hyphens (`workflows:plan` -> `workflows-plan`) preserves context while staying within valid filename characters.
15+
16+
## Key Decisions
17+
18+
- **Replace colons with hyphens** instead of stripping the prefix: `workflows:plan` -> `workflows-plan`
19+
- **Copilot only** — other converters (Cursor, Droid, etc.) keep their current flattening behavior
20+
- **Content transformation too** — slash command references in body text also use hyphens: `/workflows:plan` -> `/workflows-plan`
21+
22+
## Changes Required
23+
24+
1. `src/converters/claude-to-copilot.ts` — change `flattenCommandName` to replace colons with hyphens
25+
2. `src/converters/claude-to-copilot.ts` — update `transformContentForCopilot` slash command rewriting
26+
3. `tests/copilot-converter.test.ts` — update affected tests
27+
28+
## Next Steps
29+
30+
-> Implement directly (small, well-scoped change)

src/converters/claude-to-copilot.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,13 @@ export function transformContentForCopilot(body: string): string {
113113
return `${prefix}Use the ${skillName} skill to: ${args.trim()}`
114114
})
115115

116-
// 2. Transform slash command references (flatten namespaces)
116+
// 2. Transform slash command references (replace colons with hyphens)
117117
const slashCommandPattern = /(?<![:\w])\/([a-z][a-z0-9_:-]*?)(?=[\s,."')\]}`]|$)/gi
118118
result = result.replace(slashCommandPattern, (match, commandName: string) => {
119119
if (commandName.includes("/")) return match
120120
if (["dev", "tmp", "etc", "usr", "var", "bin", "home"].includes(commandName)) return match
121-
const flattened = flattenCommandName(commandName)
122-
return `/${flattened}`
121+
const normalized = flattenCommandName(commandName)
122+
return `/${normalized}`
123123
})
124124

125125
// 3. Rewrite .claude/ paths to .github/ and ~/.claude/ to ~/.copilot/
@@ -179,9 +179,7 @@ function prefixEnvVars(env: Record<string, string>): Record<string, string> {
179179
}
180180

181181
function flattenCommandName(name: string): string {
182-
const colonIndex = name.lastIndexOf(":")
183-
const base = colonIndex >= 0 ? name.slice(colonIndex + 1) : name
184-
return normalizeName(base)
182+
return normalizeName(name)
185183
}
186184

187185
function normalizeName(value: string): string {

tests/copilot-converter.test.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -169,20 +169,46 @@ describe("convertClaudeToCopilot", () => {
169169

170170
expect(bundle.generatedSkills).toHaveLength(1)
171171
const skill = bundle.generatedSkills[0]
172-
expect(skill.name).toBe("plan")
172+
expect(skill.name).toBe("workflows-plan")
173173

174174
const parsed = parseFrontmatter(skill.content)
175-
expect(parsed.data.name).toBe("plan")
175+
expect(parsed.data.name).toBe("workflows-plan")
176176
expect(parsed.data.description).toBe("Planning command")
177177
expect(parsed.body).toContain("Plan the work.")
178178
})
179179

180-
test("flattens namespaced command names", () => {
180+
test("preserves namespaced command names with hyphens", () => {
181181
const bundle = convertClaudeToCopilot(fixturePlugin, defaultOptions)
182-
expect(bundle.generatedSkills[0].name).toBe("plan")
182+
expect(bundle.generatedSkills[0].name).toBe("workflows-plan")
183183
})
184184

185-
test("command name collision after flattening is deduplicated", () => {
185+
test("command name collision after normalization is deduplicated", () => {
186+
const plugin: ClaudePlugin = {
187+
...fixturePlugin,
188+
commands: [
189+
{
190+
name: "workflows:plan",
191+
description: "Workflow plan",
192+
body: "Plan body.",
193+
sourcePath: "/tmp/plugin/commands/workflows/plan.md",
194+
},
195+
{
196+
name: "workflows:plan",
197+
description: "Duplicate plan",
198+
body: "Duplicate body.",
199+
sourcePath: "/tmp/plugin/commands/workflows/plan2.md",
200+
},
201+
],
202+
agents: [],
203+
skills: [],
204+
}
205+
206+
const bundle = convertClaudeToCopilot(plugin, defaultOptions)
207+
const names = bundle.generatedSkills.map((s) => s.name)
208+
expect(names).toEqual(["workflows-plan", "workflows-plan-2"])
209+
})
210+
211+
test("namespaced and non-namespaced commands produce distinct names", () => {
186212
const plugin: ClaudePlugin = {
187213
...fixturePlugin,
188214
commands: [
@@ -205,7 +231,7 @@ describe("convertClaudeToCopilot", () => {
205231

206232
const bundle = convertClaudeToCopilot(plugin, defaultOptions)
207233
const names = bundle.generatedSkills.map((s) => s.name)
208-
expect(names).toEqual(["plan", "plan-2"])
234+
expect(names).toEqual(["workflows-plan", "plan"])
209235
})
210236

211237
test("command allowedTools is silently dropped", () => {
@@ -418,14 +444,14 @@ Task best-practices-researcher(topic)`
418444
expect(result).not.toContain("Task repo-research-analyst(")
419445
})
420446

421-
test("flattens slash commands", () => {
447+
test("replaces colons with hyphens in slash commands", () => {
422448
const input = `1. Run /deepen-plan to enhance
423449
2. Start /workflows:work to implement
424450
3. File at /tmp/output.md`
425451

426452
const result = transformContentForCopilot(input)
427453
expect(result).toContain("/deepen-plan")
428-
expect(result).toContain("/work")
454+
expect(result).toContain("/workflows-work")
429455
expect(result).not.toContain("/workflows:work")
430456
// File paths preserved
431457
expect(result).toContain("/tmp/output.md")

0 commit comments

Comments
 (0)