diff --git a/docs/brainstorms/2026-02-17-copilot-skill-naming-brainstorm.md b/docs/brainstorms/2026-02-17-copilot-skill-naming-brainstorm.md new file mode 100644 index 0000000..c04e97d --- /dev/null +++ b/docs/brainstorms/2026-02-17-copilot-skill-naming-brainstorm.md @@ -0,0 +1,30 @@ +--- +date: 2026-02-17 +topic: copilot-skill-naming +--- + +# Copilot Skill Naming: Preserve Namespace + +## What We're Building + +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. + +## Why This Approach + +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. + +## Key Decisions + +- **Replace colons with hyphens** instead of stripping the prefix: `workflows:plan` -> `workflows-plan` +- **Copilot only** — other converters (Cursor, Droid, etc.) keep their current flattening behavior +- **Content transformation too** — slash command references in body text also use hyphens: `/workflows:plan` -> `/workflows-plan` + +## Changes Required + +1. `src/converters/claude-to-copilot.ts` — change `flattenCommandName` to replace colons with hyphens +2. `src/converters/claude-to-copilot.ts` — update `transformContentForCopilot` slash command rewriting +3. `tests/copilot-converter.test.ts` — update affected tests + +## Next Steps + +-> Implement directly (small, well-scoped change) diff --git a/src/converters/claude-to-copilot.ts b/src/converters/claude-to-copilot.ts index 510bfa9..6a7722c 100644 --- a/src/converters/claude-to-copilot.ts +++ b/src/converters/claude-to-copilot.ts @@ -113,13 +113,13 @@ export function transformContentForCopilot(body: string): string { return `${prefix}Use the ${skillName} skill to: ${args.trim()}` }) - // 2. Transform slash command references (flatten namespaces) + // 2. Transform slash command references (replace colons with hyphens) const slashCommandPattern = /(? { if (commandName.includes("/")) return match if (["dev", "tmp", "etc", "usr", "var", "bin", "home"].includes(commandName)) return match - const flattened = flattenCommandName(commandName) - return `/${flattened}` + const normalized = flattenCommandName(commandName) + return `/${normalized}` }) // 3. Rewrite .claude/ paths to .github/ and ~/.claude/ to ~/.copilot/ @@ -179,9 +179,7 @@ function prefixEnvVars(env: Record): Record { } function flattenCommandName(name: string): string { - const colonIndex = name.lastIndexOf(":") - const base = colonIndex >= 0 ? name.slice(colonIndex + 1) : name - return normalizeName(base) + return normalizeName(name) } function normalizeName(value: string): string { diff --git a/tests/copilot-converter.test.ts b/tests/copilot-converter.test.ts index bbb37bd..22f7973 100644 --- a/tests/copilot-converter.test.ts +++ b/tests/copilot-converter.test.ts @@ -169,20 +169,46 @@ describe("convertClaudeToCopilot", () => { expect(bundle.generatedSkills).toHaveLength(1) const skill = bundle.generatedSkills[0] - expect(skill.name).toBe("plan") + expect(skill.name).toBe("workflows-plan") const parsed = parseFrontmatter(skill.content) - expect(parsed.data.name).toBe("plan") + expect(parsed.data.name).toBe("workflows-plan") expect(parsed.data.description).toBe("Planning command") expect(parsed.body).toContain("Plan the work.") }) - test("flattens namespaced command names", () => { + test("preserves namespaced command names with hyphens", () => { const bundle = convertClaudeToCopilot(fixturePlugin, defaultOptions) - expect(bundle.generatedSkills[0].name).toBe("plan") + expect(bundle.generatedSkills[0].name).toBe("workflows-plan") }) - test("command name collision after flattening is deduplicated", () => { + test("command name collision after normalization is deduplicated", () => { + const plugin: ClaudePlugin = { + ...fixturePlugin, + commands: [ + { + name: "workflows:plan", + description: "Workflow plan", + body: "Plan body.", + sourcePath: "/tmp/plugin/commands/workflows/plan.md", + }, + { + name: "workflows:plan", + description: "Duplicate plan", + body: "Duplicate body.", + sourcePath: "/tmp/plugin/commands/workflows/plan2.md", + }, + ], + agents: [], + skills: [], + } + + const bundle = convertClaudeToCopilot(plugin, defaultOptions) + const names = bundle.generatedSkills.map((s) => s.name) + expect(names).toEqual(["workflows-plan", "workflows-plan-2"]) + }) + + test("namespaced and non-namespaced commands produce distinct names", () => { const plugin: ClaudePlugin = { ...fixturePlugin, commands: [ @@ -205,7 +231,7 @@ describe("convertClaudeToCopilot", () => { const bundle = convertClaudeToCopilot(plugin, defaultOptions) const names = bundle.generatedSkills.map((s) => s.name) - expect(names).toEqual(["plan", "plan-2"]) + expect(names).toEqual(["workflows-plan", "plan"]) }) test("command allowedTools is silently dropped", () => { @@ -418,14 +444,14 @@ Task best-practices-researcher(topic)` expect(result).not.toContain("Task repo-research-analyst(") }) - test("flattens slash commands", () => { + test("replaces colons with hyphens in slash commands", () => { const input = `1. Run /deepen-plan to enhance 2. Start /workflows:work to implement 3. File at /tmp/output.md` const result = transformContentForCopilot(input) expect(result).toContain("/deepen-plan") - expect(result).toContain("/work") + expect(result).toContain("/workflows-work") expect(result).not.toContain("/workflows:work") // File paths preserved expect(result).toContain("/tmp/output.md")