@every-env/compound-plugin 0.7.0 → 0.8.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.
Files changed (33) hide show
  1. package/.cursor-plugin/marketplace.json +25 -0
  2. package/CHANGELOG.md +13 -0
  3. package/README.md +14 -8
  4. package/bun.lock +1 -0
  5. package/docs/brainstorms/2026-02-14-copilot-converter-target-brainstorm.md +117 -0
  6. package/docs/brainstorms/2026-02-17-copilot-skill-naming-brainstorm.md +30 -0
  7. package/docs/plans/2026-02-14-feat-add-copilot-converter-target-plan.md +328 -0
  8. package/docs/specs/copilot.md +122 -0
  9. package/package.json +1 -1
  10. package/plugins/coding-tutor/.cursor-plugin/plugin.json +21 -0
  11. package/plugins/compound-engineering/.claude-plugin/plugin.json +1 -1
  12. package/plugins/compound-engineering/.cursor-plugin/plugin.json +31 -0
  13. package/plugins/compound-engineering/.mcp.json +8 -0
  14. package/plugins/compound-engineering/CHANGELOG.md +10 -0
  15. package/plugins/compound-engineering/commands/lfg.md +3 -3
  16. package/plugins/compound-engineering/commands/slfg.md +2 -2
  17. package/plugins/compound-engineering/commands/workflows/plan.md +15 -1
  18. package/src/commands/install.ts +5 -1
  19. package/src/commands/sync.ts +8 -8
  20. package/src/converters/{claude-to-cursor.ts → claude-to-copilot.ts} +93 -49
  21. package/src/sync/{cursor.ts → copilot.ts} +36 -14
  22. package/src/targets/copilot.ts +48 -0
  23. package/src/targets/index.ts +9 -9
  24. package/src/types/copilot.ts +31 -0
  25. package/src/utils/frontmatter.ts +1 -1
  26. package/tests/copilot-converter.test.ts +467 -0
  27. package/tests/copilot-writer.test.ts +189 -0
  28. package/tests/sync-copilot.test.ts +148 -0
  29. package/src/targets/cursor.ts +0 -48
  30. package/src/types/cursor.ts +0 -29
  31. package/tests/cursor-converter.test.ts +0 -347
  32. package/tests/cursor-writer.test.ts +0 -137
  33. package/tests/sync-cursor.test.ts +0 -92
@@ -0,0 +1,122 @@
1
+ # GitHub Copilot Spec (Agents, Skills, MCP)
2
+
3
+ Last verified: 2026-02-14
4
+
5
+ ## Primary sources
6
+
7
+ ```
8
+ https://docs.github.com/en/copilot/reference/custom-agents-configuration
9
+ https://docs.github.com/en/copilot/concepts/agents/about-agent-skills
10
+ https://docs.github.com/en/copilot/concepts/agents/coding-agent/mcp-and-coding-agent
11
+ ```
12
+
13
+ ## Config locations
14
+
15
+ | Scope | Path |
16
+ |-------|------|
17
+ | Project agents | `.github/agents/*.agent.md` |
18
+ | Project skills | `.github/skills/*/SKILL.md` |
19
+ | Project instructions | `.github/copilot-instructions.md` |
20
+ | Path-specific instructions | `.github/instructions/*.instructions.md` |
21
+ | Project prompts | `.github/prompts/*.prompt.md` |
22
+ | Org/enterprise agents | `.github-private/agents/*.agent.md` |
23
+ | Personal skills | `~/.copilot/skills/*/SKILL.md` |
24
+ | Directory instructions | `AGENTS.md` (nearest ancestor wins) |
25
+
26
+ ## Agents (.agent.md files)
27
+
28
+ - Custom agents are Markdown files with YAML frontmatter stored in `.github/agents/`.
29
+ - File extension is `.agent.md` (or `.md`). Filenames may only contain: `.`, `-`, `_`, `a-z`, `A-Z`, `0-9`.
30
+ - `description` is the only required frontmatter field.
31
+
32
+ ### Frontmatter fields
33
+
34
+ | Field | Required | Default | Description |
35
+ |-------|----------|---------|-------------|
36
+ | `name` | No | Derived from filename | Display name |
37
+ | `description` | **Yes** | — | What the agent does |
38
+ | `tools` | No | `["*"]` | Tool access list. `[]` disables all tools. |
39
+ | `target` | No | both | `vscode`, `github-copilot`, or omit for both |
40
+ | `infer` | No | `true` | Auto-select based on task context |
41
+ | `model` | No | Platform default | AI model (works in IDE, may be ignored on github.com) |
42
+ | `mcp-servers` | No | — | MCP config (org/enterprise agents only) |
43
+ | `metadata` | No | — | Arbitrary key-value annotations |
44
+
45
+ ### Character limit
46
+
47
+ Agent body content is limited to **30,000 characters**.
48
+
49
+ ### Tool names
50
+
51
+ | Name | Aliases | Purpose |
52
+ |------|---------|---------|
53
+ | `execute` | `shell`, `Bash` | Run shell commands |
54
+ | `read` | `Read` | Read files |
55
+ | `edit` | `Edit`, `Write` | Modify files |
56
+ | `search` | `Grep`, `Glob` | Search files |
57
+ | `agent` | `Task` | Invoke other agents |
58
+ | `web` | `WebSearch`, `WebFetch` | Web access |
59
+
60
+ ## Skills (SKILL.md)
61
+
62
+ - Skills follow the open SKILL.md standard (same format as Claude Code and Cursor).
63
+ - A skill is a directory containing `SKILL.md` plus optional `scripts/`, `references/`, and `assets/`.
64
+ - YAML frontmatter requires `name` and `description` fields.
65
+ - Skills are loaded on-demand when Copilot determines relevance.
66
+
67
+ ### Discovery locations
68
+
69
+ | Scope | Path |
70
+ |-------|------|
71
+ | Project | `.github/skills/*/SKILL.md` |
72
+ | Project (Claude-compatible) | `.claude/skills/*/SKILL.md` |
73
+ | Project (auto-discovery) | `.agents/skills/*/SKILL.md` |
74
+ | Personal | `~/.copilot/skills/*/SKILL.md` |
75
+
76
+ ## MCP (Model Context Protocol)
77
+
78
+ - MCP configuration is set via **Repository Settings > Copilot > Coding agent > MCP configuration** on GitHub.
79
+ - Repository-level agents **cannot** define MCP servers inline; use repository settings instead.
80
+ - Org/enterprise agents can embed MCP server definitions in frontmatter.
81
+ - All env var names must use the `COPILOT_MCP_` prefix.
82
+ - Only MCP tools are supported (not resources or prompts).
83
+
84
+ ### Config structure
85
+
86
+ ```json
87
+ {
88
+ "mcpServers": {
89
+ "server-name": {
90
+ "type": "local",
91
+ "command": "npx",
92
+ "args": ["package"],
93
+ "tools": ["*"],
94
+ "env": {
95
+ "API_KEY": "COPILOT_MCP_API_KEY"
96
+ }
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ ### Server types
103
+
104
+ | Type | Fields |
105
+ |------|--------|
106
+ | Local/stdio | `type: "local"`, `command`, `args`, `tools`, `env` |
107
+ | Remote/SSE | `type: "sse"`, `url`, `tools`, `headers` |
108
+
109
+ ## Prompts (.prompt.md)
110
+
111
+ - Reusable prompt files stored in `.github/prompts/`.
112
+ - Available in VS Code, Visual Studio, and JetBrains IDEs only (not on github.com).
113
+ - Invoked via `/promptname` in chat.
114
+ - Support variable syntax: `${input:name}`, `${file}`, `${selection}`.
115
+
116
+ ## Precedence
117
+
118
+ 1. Repository-level agents
119
+ 2. Organization-level agents (`.github-private`)
120
+ 3. Enterprise-level agents (`.github-private`)
121
+
122
+ Within a repo, `AGENTS.md` files in directories provide nearest-ancestor-wins instructions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@every-env/compound-plugin",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "bin": {
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "coding-tutor",
3
+ "displayName": "Coding Tutor",
4
+ "version": "1.2.1",
5
+ "description": "Personalized coding tutorials that use your actual codebase for examples with spaced repetition quizzes",
6
+ "author": {
7
+ "name": "Nityesh Agarwal"
8
+ },
9
+ "homepage": "https://github.com/EveryInc/compound-engineering-plugin",
10
+ "repository": "https://github.com/EveryInc/compound-engineering-plugin",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "cursor",
14
+ "plugin",
15
+ "coding",
16
+ "programming",
17
+ "tutorial",
18
+ "learning",
19
+ "spaced-repetition"
20
+ ]
21
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compound-engineering",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "AI-powered development tools. 29 agents, 22 commands, 19 skills, 1 MCP server for code review, research, design, and workflow automation.",
5
5
  "author": {
6
6
  "name": "Kieran Klaassen",
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "compound-engineering",
3
+ "displayName": "Compound Engineering",
4
+ "version": "2.33.0",
5
+ "description": "AI-powered development tools. 29 agents, 22 commands, 19 skills, 1 MCP server for code review, research, design, and workflow automation.",
6
+ "author": {
7
+ "name": "Kieran Klaassen",
8
+ "email": "kieran@every.to",
9
+ "url": "https://github.com/kieranklaassen"
10
+ },
11
+ "homepage": "https://every.to/source-code/my-ai-had-already-fixed-the-code-before-i-saw-it",
12
+ "repository": "https://github.com/EveryInc/compound-engineering-plugin",
13
+ "license": "MIT",
14
+ "keywords": [
15
+ "cursor",
16
+ "plugin",
17
+ "ai-powered",
18
+ "compound-engineering",
19
+ "workflow-automation",
20
+ "code-review",
21
+ "rails",
22
+ "ruby",
23
+ "python",
24
+ "typescript",
25
+ "knowledge-management",
26
+ "image-generation",
27
+ "agent-browser",
28
+ "browser-automation"
29
+ ],
30
+ "mcpServers": ".mcp.json"
31
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "context7": {
4
+ "type": "http",
5
+ "url": "https://mcp.context7.com/mcp"
6
+ }
7
+ }
8
+ }
@@ -5,6 +5,16 @@ All notable changes to the compound-engineering plugin will be documented in thi
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.35.0] - 2026-02-17
9
+
10
+ ### Fixed
11
+
12
+ - **`/lfg` and `/slfg` first-run failures** — Made ralph-loop step optional with graceful fallback when `ralph-wiggum` skill is not installed (#154). Added explicit "do not stop" instruction across all steps (#134).
13
+ - **`/workflows:plan` not writing file in pipeline** — Added mandatory "Write Plan File" step with explicit Write tool instructions before Post-Generation Options. The file is now always written to disk before any interactive prompts (#155). Also adds pipeline-mode note to skip AskUserQuestion calls when invoked from LFG/SLFG (#134).
14
+ - **Agent namespace typo in `/workflows:plan`** — `Task spec-flow-analyzer(...)` now uses the full qualified name `Task compound-engineering:workflow:spec-flow-analyzer(...)` to prevent Claude from prepending the wrong `workflows:` prefix (#193).
15
+
16
+ ---
17
+
8
18
  ## [2.34.0] - 2026-02-14
9
19
 
10
20
  ### Added
@@ -5,9 +5,9 @@ argument-hint: "[feature description]"
5
5
  disable-model-invocation: true
6
6
  ---
7
7
 
8
- Run these slash commands in order. Do not do anything else.
8
+ Run these slash commands in order. Do not do anything else. Do not stop between steps — complete every step through to the end.
9
9
 
10
- 1. `/ralph-wiggum:ralph-loop "finish all slash commands" --completion-promise "DONE"`
10
+ 1. **Optional:** If the `ralph-wiggum` skill is available, run `/ralph-wiggum:ralph-loop "finish all slash commands" --completion-promise "DONE"`. If not available or it fails, skip and continue to step 2 immediately.
11
11
  2. `/workflows:plan $ARGUMENTS`
12
12
  3. `/compound-engineering:deepen-plan`
13
13
  4. `/workflows:work`
@@ -17,4 +17,4 @@ Run these slash commands in order. Do not do anything else.
17
17
  8. `/compound-engineering:feature-video`
18
18
  9. Output `<promise>DONE</promise>` when video is in PR
19
19
 
20
- Start with step 1 now.
20
+ Start with step 2 now (or step 1 if ralph-wiggum is available).
@@ -5,11 +5,11 @@ argument-hint: "[feature description]"
5
5
  disable-model-invocation: true
6
6
  ---
7
7
 
8
- Swarm-enabled LFG. Run these steps in order, parallelizing where indicated.
8
+ Swarm-enabled LFG. Run these steps in order, parallelizing where indicated. Do not stop between steps — complete every step through to the end.
9
9
 
10
10
  ## Sequential Phase
11
11
 
12
- 1. `/ralph-wiggum:ralph-loop "finish all slash commands" --completion-promise "DONE"`
12
+ 1. **Optional:** If the `ralph-wiggum` skill is available, run `/ralph-wiggum:ralph-loop "finish all slash commands" --completion-promise "DONE"`. If not available or it fails, skip and continue to step 2 immediately.
13
13
  2. `/workflows:plan $ARGUMENTS`
14
14
  3. `/compound-engineering:deepen-plan`
15
15
  4. `/workflows:work` — **Use swarm mode**: Make a Task list and launch an army of agent swarm subagents to build the plan
@@ -150,7 +150,7 @@ Think like a product manager - what would make this issue clear and actionable?
150
150
 
151
151
  After planning the issue structure, run SpecFlow Analyzer to validate and refine the feature specification:
152
152
 
153
- - Task spec-flow-analyzer(feature_description, research_findings)
153
+ - Task compound-engineering:workflow:spec-flow-analyzer(feature_description, research_findings)
154
154
 
155
155
  **SpecFlow Analyzer Output:**
156
156
 
@@ -475,6 +475,20 @@ end
475
475
  - [ ] Add names of files in pseudo code examples and todo lists
476
476
  - [ ] Add an ERD mermaid diagram if applicable for new model changes
477
477
 
478
+ ## Write Plan File
479
+
480
+ **REQUIRED: Write the plan file to disk before presenting any options.**
481
+
482
+ ```bash
483
+ mkdir -p docs/plans/
484
+ ```
485
+
486
+ Use the Write tool to save the complete plan to `docs/plans/YYYY-MM-DD-<type>-<descriptive-name>-plan.md`. This step is mandatory and cannot be skipped — even when running as part of LFG/SLFG or other automated pipelines.
487
+
488
+ Confirm: "Plan written to docs/plans/[filename]"
489
+
490
+ **Pipeline mode:** If invoked from an automated workflow (LFG, SLFG, or any `disable-model-invocation` context), skip all AskUserQuestion calls. Make decisions automatically and proceed to writing the plan without interactive prompts.
491
+
478
492
  ## Output Format
479
493
 
480
494
  **Filename:** Use the date and kebab-case filename from Step 2 Title & Categorization.
@@ -25,7 +25,7 @@ export default defineCommand({
25
25
  to: {
26
26
  type: "string",
27
27
  default: "opencode",
28
- description: "Target format (opencode | codex | droid | cursor | pi | gemini)",
28
+ description: "Target format (opencode | codex | droid | cursor | pi | copilot | gemini)",
29
29
  },
30
30
  output: {
31
31
  type: "string",
@@ -187,6 +187,10 @@ function resolveTargetOutputRoot(
187
187
  const base = hasExplicitOutput ? outputRoot : process.cwd()
188
188
  return path.join(base, ".gemini")
189
189
  }
190
+ if (targetName === "copilot") {
191
+ const base = hasExplicitOutput ? outputRoot : process.cwd()
192
+ return path.join(base, ".github")
193
+ }
190
194
  return outputRoot
191
195
  }
192
196
 
@@ -6,10 +6,10 @@ import { syncToOpenCode } from "../sync/opencode"
6
6
  import { syncToCodex } from "../sync/codex"
7
7
  import { syncToPi } from "../sync/pi"
8
8
  import { syncToDroid } from "../sync/droid"
9
- import { syncToCursor } from "../sync/cursor"
9
+ import { syncToCopilot } from "../sync/copilot"
10
10
  import { expandHome } from "../utils/resolve-home"
11
11
 
12
- const validTargets = ["opencode", "codex", "pi", "droid", "cursor"] as const
12
+ const validTargets = ["opencode", "codex", "pi", "droid", "copilot"] as const
13
13
  type SyncTarget = (typeof validTargets)[number]
14
14
 
15
15
  function isValidTarget(value: string): value is SyncTarget {
@@ -40,21 +40,21 @@ function resolveOutputRoot(target: SyncTarget): string {
40
40
  return path.join(os.homedir(), ".pi", "agent")
41
41
  case "droid":
42
42
  return path.join(os.homedir(), ".factory")
43
- case "cursor":
44
- return path.join(process.cwd(), ".cursor")
43
+ case "copilot":
44
+ return path.join(process.cwd(), ".github")
45
45
  }
46
46
  }
47
47
 
48
48
  export default defineCommand({
49
49
  meta: {
50
50
  name: "sync",
51
- description: "Sync Claude Code config (~/.claude/) to OpenCode, Codex, Pi, Droid, or Cursor",
51
+ description: "Sync Claude Code config (~/.claude/) to OpenCode, Codex, Pi, Droid, or Copilot",
52
52
  },
53
53
  args: {
54
54
  target: {
55
55
  type: "string",
56
56
  required: true,
57
- description: "Target: opencode | codex | pi | droid | cursor",
57
+ description: "Target: opencode | codex | pi | droid | copilot",
58
58
  },
59
59
  claudeHome: {
60
60
  type: "string",
@@ -97,8 +97,8 @@ export default defineCommand({
97
97
  case "droid":
98
98
  await syncToDroid(config, outputRoot)
99
99
  break
100
- case "cursor":
101
- await syncToCursor(config, outputRoot)
100
+ case "copilot":
101
+ await syncToCopilot(config, outputRoot)
102
102
  break
103
103
  }
104
104
 
@@ -1,43 +1,63 @@
1
1
  import { formatFrontmatter } from "../utils/frontmatter"
2
2
  import type { ClaudeAgent, ClaudeCommand, ClaudeMcpServer, ClaudePlugin } from "../types/claude"
3
- import type { CursorBundle, CursorCommand, CursorMcpServer, CursorRule } from "../types/cursor"
3
+ import type {
4
+ CopilotAgent,
5
+ CopilotBundle,
6
+ CopilotGeneratedSkill,
7
+ CopilotMcpServer,
8
+ } from "../types/copilot"
4
9
  import type { ClaudeToOpenCodeOptions } from "./claude-to-opencode"
5
10
 
6
- export type ClaudeToCursorOptions = ClaudeToOpenCodeOptions
11
+ export type ClaudeToCopilotOptions = ClaudeToOpenCodeOptions
7
12
 
8
- export function convertClaudeToCursor(
13
+ const COPILOT_BODY_CHAR_LIMIT = 30_000
14
+
15
+ export function convertClaudeToCopilot(
9
16
  plugin: ClaudePlugin,
10
- _options: ClaudeToCursorOptions,
11
- ): CursorBundle {
12
- const usedRuleNames = new Set<string>()
13
- const usedCommandNames = new Set<string>()
17
+ _options: ClaudeToCopilotOptions,
18
+ ): CopilotBundle {
19
+ const usedAgentNames = new Set<string>()
20
+ const usedSkillNames = new Set<string>()
21
+
22
+ const agents = plugin.agents.map((agent) => convertAgent(agent, usedAgentNames))
23
+
24
+ // Reserve skill names first so generated skills (from commands) don't collide
25
+ const skillDirs = plugin.skills.map((skill) => {
26
+ usedSkillNames.add(skill.name)
27
+ return {
28
+ name: skill.name,
29
+ sourceDir: skill.sourceDir,
30
+ }
31
+ })
14
32
 
15
- const rules = plugin.agents.map((agent) => convertAgentToRule(agent, usedRuleNames))
16
- const commands = plugin.commands.map((command) => convertCommand(command, usedCommandNames))
17
- const skillDirs = plugin.skills.map((skill) => ({
18
- name: skill.name,
19
- sourceDir: skill.sourceDir,
20
- }))
33
+ const generatedSkills = plugin.commands.map((command) =>
34
+ convertCommandToSkill(command, usedSkillNames),
35
+ )
21
36
 
22
- const mcpServers = convertMcpServers(plugin.mcpServers)
37
+ const mcpConfig = convertMcpServers(plugin.mcpServers)
23
38
 
24
39
  if (plugin.hooks && Object.keys(plugin.hooks.hooks).length > 0) {
25
- console.warn("Warning: Cursor does not support hooks. Hooks were skipped during conversion.")
40
+ console.warn("Warning: Copilot does not support hooks. Hooks were skipped during conversion.")
26
41
  }
27
42
 
28
- return { rules, commands, skillDirs, mcpServers }
43
+ return { agents, generatedSkills, skillDirs, mcpConfig }
29
44
  }
30
45
 
31
- function convertAgentToRule(agent: ClaudeAgent, usedNames: Set<string>): CursorRule {
46
+ function convertAgent(agent: ClaudeAgent, usedNames: Set<string>): CopilotAgent {
32
47
  const name = uniqueName(normalizeName(agent.name), usedNames)
33
48
  const description = agent.description ?? `Converted from Claude agent ${agent.name}`
34
49
 
35
50
  const frontmatter: Record<string, unknown> = {
36
51
  description,
37
- alwaysApply: false,
52
+ tools: ["*"],
53
+ infer: true,
38
54
  }
39
55
 
40
- let body = transformContentForCursor(agent.body.trim())
56
+ if (agent.model) {
57
+ frontmatter.model = agent.model
58
+ }
59
+
60
+ let body = transformContentForCopilot(agent.body.trim())
41
61
  if (agent.capabilities && agent.capabilities.length > 0) {
42
62
  const capabilities = agent.capabilities.map((c) => `- ${c}`).join("\n")
43
63
  body = `## Capabilities\n${capabilities}\n\n${body}`.trim()
@@ -46,39 +66,44 @@ function convertAgentToRule(agent: ClaudeAgent, usedNames: Set<string>): CursorR
46
66
  body = `Instructions converted from the ${agent.name} agent.`
47
67
  }
48
68
 
69
+ if (body.length > COPILOT_BODY_CHAR_LIMIT) {
70
+ console.warn(
71
+ `Warning: Agent "${agent.name}" body exceeds ${COPILOT_BODY_CHAR_LIMIT} characters (${body.length}). Copilot may truncate it.`,
72
+ )
73
+ }
74
+
49
75
  const content = formatFrontmatter(frontmatter, body)
50
76
  return { name, content }
51
77
  }
52
78
 
53
- function convertCommand(command: ClaudeCommand, usedNames: Set<string>): CursorCommand {
79
+ function convertCommandToSkill(
80
+ command: ClaudeCommand,
81
+ usedNames: Set<string>,
82
+ ): CopilotGeneratedSkill {
54
83
  const name = uniqueName(flattenCommandName(command.name), usedNames)
55
84
 
56
- const sections: string[] = []
57
-
85
+ const frontmatter: Record<string, unknown> = {
86
+ name,
87
+ }
58
88
  if (command.description) {
59
- sections.push(`<!-- ${command.description} -->`)
89
+ frontmatter.description = command.description
60
90
  }
61
91
 
92
+ const sections: string[] = []
93
+
62
94
  if (command.argumentHint) {
63
95
  sections.push(`## Arguments\n${command.argumentHint}`)
64
96
  }
65
97
 
66
- const transformedBody = transformContentForCursor(command.body.trim())
98
+ const transformedBody = transformContentForCopilot(command.body.trim())
67
99
  sections.push(transformedBody)
68
100
 
69
- const content = sections.filter(Boolean).join("\n\n").trim()
101
+ const body = sections.filter(Boolean).join("\n\n").trim()
102
+ const content = formatFrontmatter(frontmatter, body)
70
103
  return { name, content }
71
104
  }
72
105
 
73
- /**
74
- * Transform Claude Code content to Cursor-compatible content.
75
- *
76
- * 1. Task agent calls: Task agent-name(args) -> Use the agent-name skill to: args
77
- * 2. Slash commands: /workflows:plan -> /plan (flatten namespace)
78
- * 3. Path rewriting: .claude/ -> .cursor/
79
- * 4. Agent references: @agent-name -> the agent-name rule
80
- */
81
- export function transformContentForCursor(body: string): string {
106
+ export function transformContentForCopilot(body: string): string {
82
107
  let result = body
83
108
 
84
109
  // 1. Transform Task agent calls
@@ -88,24 +113,25 @@ export function transformContentForCursor(body: string): string {
88
113
  return `${prefix}Use the ${skillName} skill to: ${args.trim()}`
89
114
  })
90
115
 
91
- // 2. Transform slash command references (flatten namespaces)
116
+ // 2. Transform slash command references (replace colons with hyphens)
92
117
  const slashCommandPattern = /(?<![:\w])\/([a-z][a-z0-9_:-]*?)(?=[\s,."')\]}`]|$)/gi
93
118
  result = result.replace(slashCommandPattern, (match, commandName: string) => {
94
119
  if (commandName.includes("/")) return match
95
120
  if (["dev", "tmp", "etc", "usr", "var", "bin", "home"].includes(commandName)) return match
96
- const flattened = flattenCommandName(commandName)
97
- return `/${flattened}`
121
+ const normalized = flattenCommandName(commandName)
122
+ return `/${normalized}`
98
123
  })
99
124
 
100
- // 3. Rewrite .claude/ paths to .cursor/
125
+ // 3. Rewrite .claude/ paths to .github/ and ~/.claude/ to ~/.copilot/
101
126
  result = result
102
- .replace(/~\/\.claude\//g, "~/.cursor/")
103
- .replace(/\.claude\//g, ".cursor/")
127
+ .replace(/~\/\.claude\//g, "~/.copilot/")
128
+ .replace(/\.claude\//g, ".github/")
104
129
 
105
130
  // 4. Transform @agent-name references
106
- const agentRefPattern = /@([a-z][a-z0-9-]*-(?:agent|reviewer|researcher|analyst|specialist|oracle|sentinel|guardian|strategist))/gi
131
+ const agentRefPattern =
132
+ /@([a-z][a-z0-9-]*-(?:agent|reviewer|researcher|analyst|specialist|oracle|sentinel|guardian|strategist))/gi
107
133
  result = result.replace(agentRefPattern, (_match, agentName: string) => {
108
- return `the ${normalizeName(agentName)} rule`
134
+ return `the ${normalizeName(agentName)} agent`
109
135
  })
110
136
 
111
137
  return result
@@ -113,29 +139,47 @@ export function transformContentForCursor(body: string): string {
113
139
 
114
140
  function convertMcpServers(
115
141
  servers?: Record<string, ClaudeMcpServer>,
116
- ): Record<string, CursorMcpServer> | undefined {
142
+ ): Record<string, CopilotMcpServer> | undefined {
117
143
  if (!servers || Object.keys(servers).length === 0) return undefined
118
144
 
119
- const result: Record<string, CursorMcpServer> = {}
145
+ const result: Record<string, CopilotMcpServer> = {}
120
146
  for (const [name, server] of Object.entries(servers)) {
121
- const entry: CursorMcpServer = {}
147
+ const entry: CopilotMcpServer = {
148
+ type: server.command ? "local" : "sse",
149
+ tools: ["*"],
150
+ }
151
+
122
152
  if (server.command) {
123
153
  entry.command = server.command
124
154
  if (server.args && server.args.length > 0) entry.args = server.args
125
- if (server.env && Object.keys(server.env).length > 0) entry.env = server.env
126
155
  } else if (server.url) {
127
156
  entry.url = server.url
128
157
  if (server.headers && Object.keys(server.headers).length > 0) entry.headers = server.headers
129
158
  }
159
+
160
+ if (server.env && Object.keys(server.env).length > 0) {
161
+ entry.env = prefixEnvVars(server.env)
162
+ }
163
+
130
164
  result[name] = entry
131
165
  }
132
166
  return result
133
167
  }
134
168
 
169
+ function prefixEnvVars(env: Record<string, string>): Record<string, string> {
170
+ const result: Record<string, string> = {}
171
+ for (const [key, value] of Object.entries(env)) {
172
+ if (key.startsWith("COPILOT_MCP_")) {
173
+ result[key] = value
174
+ } else {
175
+ result[`COPILOT_MCP_${key}`] = value
176
+ }
177
+ }
178
+ return result
179
+ }
180
+
135
181
  function flattenCommandName(name: string): string {
136
- const colonIndex = name.lastIndexOf(":")
137
- const base = colonIndex >= 0 ? name.slice(colonIndex + 1) : name
138
- return normalizeName(base)
182
+ return normalizeName(name)
139
183
  }
140
184
 
141
185
  function normalizeName(value: string): string {