Files
claude-engineering-plugin/docs/plans/2026-02-14-feat-add-copilot-converter-target-plan.md
Brayan Jules 4f7c598f27 feat: Add GitHub Copilot converter target
Add Copilot as the 6th converter target, transforming Claude Code plugins
into Copilot's native format: custom agents (.agent.md), agent skills
(SKILL.md), and MCP server configuration JSON.

Component mapping:
- Agents → .github/agents/{name}.agent.md (with Copilot frontmatter)
- Commands → .github/skills/{name}/SKILL.md
- Skills → .github/skills/{name}/ (copied as-is)
- MCP servers → .github/copilot-mcp-config.json
- Hooks → skipped with warning

Also adds `compound sync copilot` support and fixes YAML quoting for
the `*` character in frontmatter serialization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:14:40 -03:00

11 KiB

title, type, date, status
title type date status
feat: Add GitHub Copilot converter target feat 2026-02-14 complete

feat: Add GitHub Copilot Converter Target

Overview

Add GitHub Copilot as a converter target following the established TargetHandler pattern. This converts the compound-engineering Claude Code plugin into Copilot's native format: custom agents (.agent.md), agent skills (SKILL.md), and MCP server configuration JSON.

Brainstorm: docs/brainstorms/2026-02-14-copilot-converter-target-brainstorm.md

Problem Statement

The CLI tool (compound) already supports converting Claude Code plugins to 5 target formats (OpenCode, Codex, Droid, Cursor, Pi). GitHub Copilot is a widely-used AI coding assistant that now supports custom agents, skills, and MCP servers — but there's no converter target for it.

Proposed Solution

Follow the existing converter pattern exactly:

  1. Define types (src/types/copilot.ts)
  2. Implement converter (src/converters/claude-to-copilot.ts)
  3. Implement writer (src/targets/copilot.ts)
  4. Register target (src/targets/index.ts)
  5. Add sync support (src/sync/copilot.ts, src/commands/sync.ts)
  6. Write tests and documentation

Component Mapping

Claude Code Copilot Output Path
Agents (.md) Custom Agents (.agent.md) .github/agents/{name}.agent.md
Commands (.md) Agent Skills (SKILL.md) .github/skills/{name}/SKILL.md
Skills (SKILL.md) Agent Skills (SKILL.md) .github/skills/{name}/SKILL.md
MCP Servers Config JSON .github/copilot-mcp-config.json
Hooks Skipped Warning to stderr

Technical Approach

Phase 1: Types

File: src/types/copilot.ts

export type CopilotAgent = {
  name: string
  content: string // Full .agent.md content with frontmatter
}

export type CopilotGeneratedSkill = {
  name: string
  content: string // SKILL.md content with frontmatter
}

export type CopilotSkillDir = {
  name: string
  sourceDir: string
}

export type CopilotMcpServer = {
  type: string
  command?: string
  args?: string[]
  url?: string
  tools: string[]
  env?: Record<string, string>
  headers?: Record<string, string>
}

export type CopilotBundle = {
  agents: CopilotAgent[]
  generatedSkills: CopilotGeneratedSkill[]
  skillDirs: CopilotSkillDir[]
  mcpConfig?: Record<string, CopilotMcpServer>
}

Phase 2: Converter

File: src/converters/claude-to-copilot.ts

Agent conversion:

  • Frontmatter: description (required, fallback to "Converted from Claude agent {name}"), tools: ["*"], infer: true
  • Pass through model if present
  • Fold capabilities into body as ## Capabilities section (same as Cursor)
  • Use formatFrontmatter() utility
  • Warn if body exceeds 30,000 characters (.length)

Command → Skill conversion:

  • Convert to SKILL.md format with frontmatter: name, description
  • Flatten namespaced names: workflows:planplan
  • Drop allowed-tools, model, disable-model-invocation silently
  • Include argument-hint as ## Arguments section in body

Skill pass-through:

  • Map to CopilotSkillDir as-is (same as Cursor)

MCP server conversion:

  • Transform env var names: API_KEYCOPILOT_MCP_API_KEY
  • Skip vars already prefixed with COPILOT_MCP_
  • Add type: "local" for command-based servers, type: "sse" for URL-based
  • Set tools: ["*"] for all servers

Content transformation (transformContentForCopilot):

Pattern Input Output
Task calls Task repo-research-analyst(desc) Use the repo-research-analyst skill to: desc
Slash commands /workflows:plan /plan
Path rewriting .claude/ .github/
Home path rewriting ~/.claude/ ~/.copilot/
Agent references @security-sentinel the security-sentinel agent

Hooks: Warn to stderr if present, skip.

Phase 3: Writer

File: src/targets/copilot.ts

Path resolution:

  • If outputRoot basename is .github, write directly into it (avoid .github/.github/ double-nesting)
  • Otherwise, nest under .github/

Write operations:

  • Agents → .github/agents/{name}.agent.md (note: .agent.md extension)
  • Generated skills (from commands) → .github/skills/{name}/SKILL.md
  • Skill dirs → .github/skills/{name}/ (copy via copyDir)
  • MCP config → .github/copilot-mcp-config.json (backup existing with backupFile)

Phase 4: Target Registration

File: src/targets/index.ts

Add import and register:

import { convertClaudeToCopilot } from "../converters/claude-to-copilot"
import { writeCopilotBundle } from "./copilot"

// In targets record:
copilot: {
  name: "copilot",
  implemented: true,
  convert: convertClaudeToCopilot as TargetHandler<CopilotBundle>["convert"],
  write: writeCopilotBundle as TargetHandler<CopilotBundle>["write"],
},

Phase 5: Sync Support

File: src/sync/copilot.ts

Follow the Cursor sync pattern (src/sync/cursor.ts):

  • Symlink skills to .github/skills/ using forceSymlink
  • Validate skill names with isValidSkillName
  • Convert MCP servers with COPILOT_MCP_ prefix transformation
  • Merge MCP config into existing .github/copilot-mcp-config.json

File: src/commands/sync.ts

  • Add "copilot" to validTargets array
  • Add case in resolveOutputRoot(): case "copilot": return path.join(process.cwd(), ".github")
  • Add import and switch case for syncToCopilot
  • Update meta description to include "Copilot"

Phase 6: Tests

File: tests/copilot-converter.test.ts

Test cases (following tests/cursor-converter.test.ts pattern):

describe("convertClaudeToCopilot")
  ✓ converts agents to .agent.md with Copilot frontmatter
  ✓ agent description is required, fallback generated if missing
  ✓ agent with empty body gets default body
  ✓ agent capabilities are prepended to body
  ✓ agent model field is passed through
  ✓ agent tools defaults to ["*"]
  ✓ agent infer defaults to true
  ✓ warns when agent body exceeds 30k characters
  ✓ converts commands to skills with SKILL.md format
  ✓ flattens namespaced command names
  ✓ command name collision after flattening is deduplicated
  ✓ command allowedTools is silently dropped
  ✓ command with argument-hint gets Arguments section
  ✓ passes through skill directories
  ✓ skill and generated skill name collision is deduplicated
  ✓ converts MCP servers with COPILOT_MCP_ prefix
  ✓ MCP env vars already prefixed are not double-prefixed
  ✓ MCP servers get type field (local vs sse)
  ✓ warns when hooks are present
  ✓ no warning when hooks are absent
  ✓ plugin with zero agents produces empty agents array
  ✓ plugin with only skills works

describe("transformContentForCopilot")
  ✓ rewrites .claude/ paths to .github/
  ✓ rewrites ~/.claude/ paths to ~/.copilot/
  ✓ transforms Task agent calls to skill references
  ✓ flattens slash commands
  ✓ transforms @agent references to agent references

File: tests/copilot-writer.test.ts

Test cases (following tests/cursor-writer.test.ts pattern):

describe("writeCopilotBundle")
  ✓ writes agents, generated skills, copied skills, and MCP config
  ✓ agents use .agent.md file extension
  ✓ writes directly into .github output root without double-nesting
  ✓ handles empty bundles gracefully
  ✓ writes multiple agents as separate .agent.md files
  ✓ backs up existing copilot-mcp-config.json before overwriting
  ✓ creates skill directories with SKILL.md

File: tests/sync-copilot.test.ts

Test cases (following tests/sync-cursor.test.ts pattern):

describe("syncToCopilot")
  ✓ symlinks skills to .github/skills/
  ✓ skips skills with invalid names
  ✓ merges MCP config with existing file
  ✓ transforms MCP env var names to COPILOT_MCP_ prefix
  ✓ writes MCP config with restricted permissions (0o600)

Phase 7: Documentation

File: docs/specs/copilot.md

Follow docs/specs/cursor.md format:

  • Last verified date
  • Primary sources (GitHub Docs URLs)
  • Config locations table
  • Agents section (.agent.md format, frontmatter fields)
  • Skills section (SKILL.md format)
  • MCP section (config structure, env var prefix requirement)
  • Character limits (30k agent body)

File: README.md

  • Add "copilot" to the list of supported targets
  • Add usage example: compound convert --to copilot ./plugins/compound-engineering
  • Add sync example: compound sync copilot

Acceptance Criteria

Converter

  • Agents convert to .agent.md with description, tools: ["*"], infer: true
  • Agent model passes through when present
  • Agent capabilities fold into body as ## Capabilities
  • Missing description generates fallback
  • Empty body generates fallback
  • Body exceeding 30k chars triggers stderr warning
  • Commands convert to SKILL.md format
  • Command names flatten (workflows:planplan)
  • Name collisions deduplicated with -2, -3 suffix
  • Command allowed-tools dropped silently
  • Skills pass through as CopilotSkillDir
  • MCP env vars prefixed with COPILOT_MCP_
  • Already-prefixed env vars not double-prefixed
  • MCP servers get type field (local or sse)
  • Hooks trigger warning, skip conversion
  • Content transformation: Task calls, slash commands, paths, @agent refs

Writer

  • Agents written to .github/agents/{name}.agent.md
  • Generated skills written to .github/skills/{name}/SKILL.md
  • Skill dirs copied to .github/skills/{name}/
  • MCP config written to .github/copilot-mcp-config.json
  • Existing MCP config backed up before overwrite
  • No double-nesting when outputRoot is .github
  • Empty bundles handled gracefully

CLI Integration

  • compound convert --to copilot works
  • compound sync copilot works
  • Copilot registered in src/targets/index.ts
  • Sync resolves output to .github/ in current directory

Tests

  • tests/copilot-converter.test.ts — all converter tests pass
  • tests/copilot-writer.test.ts — all writer tests pass
  • tests/sync-copilot.test.ts — all sync tests pass

Documentation

  • docs/specs/copilot.md — format specification
  • README.md — updated with copilot target

Files to Create

File Purpose
src/types/copilot.ts Type definitions
src/converters/claude-to-copilot.ts Converter logic
src/targets/copilot.ts Writer logic
src/sync/copilot.ts Sync handler
tests/copilot-converter.test.ts Converter tests
tests/copilot-writer.test.ts Writer tests
tests/sync-copilot.test.ts Sync tests
docs/specs/copilot.md Format specification

Files to Modify

File Change
src/targets/index.ts Register copilot target
src/commands/sync.ts Add copilot to valid targets, output root, switch case
README.md Add copilot to supported targets

References