@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.
- package/.cursor-plugin/marketplace.json +25 -0
- package/CHANGELOG.md +13 -0
- package/README.md +14 -8
- package/bun.lock +1 -0
- package/docs/brainstorms/2026-02-14-copilot-converter-target-brainstorm.md +117 -0
- package/docs/brainstorms/2026-02-17-copilot-skill-naming-brainstorm.md +30 -0
- package/docs/plans/2026-02-14-feat-add-copilot-converter-target-plan.md +328 -0
- package/docs/specs/copilot.md +122 -0
- package/package.json +1 -1
- package/plugins/coding-tutor/.cursor-plugin/plugin.json +21 -0
- package/plugins/compound-engineering/.claude-plugin/plugin.json +1 -1
- package/plugins/compound-engineering/.cursor-plugin/plugin.json +31 -0
- package/plugins/compound-engineering/.mcp.json +8 -0
- package/plugins/compound-engineering/CHANGELOG.md +10 -0
- package/plugins/compound-engineering/commands/lfg.md +3 -3
- package/plugins/compound-engineering/commands/slfg.md +2 -2
- package/plugins/compound-engineering/commands/workflows/plan.md +15 -1
- package/src/commands/install.ts +5 -1
- package/src/commands/sync.ts +8 -8
- package/src/converters/{claude-to-cursor.ts → claude-to-copilot.ts} +93 -49
- package/src/sync/{cursor.ts → copilot.ts} +36 -14
- package/src/targets/copilot.ts +48 -0
- package/src/targets/index.ts +9 -9
- package/src/types/copilot.ts +31 -0
- package/src/utils/frontmatter.ts +1 -1
- package/tests/copilot-converter.test.ts +467 -0
- package/tests/copilot-writer.test.ts +189 -0
- package/tests/sync-copilot.test.ts +148 -0
- package/src/targets/cursor.ts +0 -48
- package/src/types/cursor.ts +0 -29
- package/tests/cursor-converter.test.ts +0 -347
- package/tests/cursor-writer.test.ts +0 -137
- 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
|
@@ -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.
|
|
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
|
+
}
|
|
@@ -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
|
|
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.
|
package/src/commands/install.ts
CHANGED
|
@@ -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
|
|
package/src/commands/sync.ts
CHANGED
|
@@ -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 {
|
|
9
|
+
import { syncToCopilot } from "../sync/copilot"
|
|
10
10
|
import { expandHome } from "../utils/resolve-home"
|
|
11
11
|
|
|
12
|
-
const validTargets = ["opencode", "codex", "pi", "droid", "
|
|
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 "
|
|
44
|
-
return path.join(process.cwd(), ".
|
|
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
|
|
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 |
|
|
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 "
|
|
101
|
-
await
|
|
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 {
|
|
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
|
|
11
|
+
export type ClaudeToCopilotOptions = ClaudeToOpenCodeOptions
|
|
7
12
|
|
|
8
|
-
|
|
13
|
+
const COPILOT_BODY_CHAR_LIMIT = 30_000
|
|
14
|
+
|
|
15
|
+
export function convertClaudeToCopilot(
|
|
9
16
|
plugin: ClaudePlugin,
|
|
10
|
-
_options:
|
|
11
|
-
):
|
|
12
|
-
const
|
|
13
|
-
const
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
37
|
+
const mcpConfig = convertMcpServers(plugin.mcpServers)
|
|
23
38
|
|
|
24
39
|
if (plugin.hooks && Object.keys(plugin.hooks.hooks).length > 0) {
|
|
25
|
-
console.warn("Warning:
|
|
40
|
+
console.warn("Warning: Copilot does not support hooks. Hooks were skipped during conversion.")
|
|
26
41
|
}
|
|
27
42
|
|
|
28
|
-
return {
|
|
43
|
+
return { agents, generatedSkills, skillDirs, mcpConfig }
|
|
29
44
|
}
|
|
30
45
|
|
|
31
|
-
function
|
|
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
|
-
|
|
52
|
+
tools: ["*"],
|
|
53
|
+
infer: true,
|
|
38
54
|
}
|
|
39
55
|
|
|
40
|
-
|
|
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
|
|
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
|
|
57
|
-
|
|
85
|
+
const frontmatter: Record<string, unknown> = {
|
|
86
|
+
name,
|
|
87
|
+
}
|
|
58
88
|
if (command.description) {
|
|
59
|
-
|
|
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 =
|
|
98
|
+
const transformedBody = transformContentForCopilot(command.body.trim())
|
|
67
99
|
sections.push(transformedBody)
|
|
68
100
|
|
|
69
|
-
const
|
|
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 (
|
|
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
|
|
97
|
-
return `/${
|
|
121
|
+
const normalized = flattenCommandName(commandName)
|
|
122
|
+
return `/${normalized}`
|
|
98
123
|
})
|
|
99
124
|
|
|
100
|
-
// 3. Rewrite .claude/ paths to .
|
|
125
|
+
// 3. Rewrite .claude/ paths to .github/ and ~/.claude/ to ~/.copilot/
|
|
101
126
|
result = result
|
|
102
|
-
.replace(/~\/\.claude\//g, "~/.
|
|
103
|
-
.replace(/\.claude\//g, ".
|
|
127
|
+
.replace(/~\/\.claude\//g, "~/.copilot/")
|
|
128
|
+
.replace(/\.claude\//g, ".github/")
|
|
104
129
|
|
|
105
130
|
// 4. Transform @agent-name references
|
|
106
|
-
const agentRefPattern =
|
|
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)}
|
|
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,
|
|
142
|
+
): Record<string, CopilotMcpServer> | undefined {
|
|
117
143
|
if (!servers || Object.keys(servers).length === 0) return undefined
|
|
118
144
|
|
|
119
|
-
const result: Record<string,
|
|
145
|
+
const result: Record<string, CopilotMcpServer> = {}
|
|
120
146
|
for (const [name, server] of Object.entries(servers)) {
|
|
121
|
-
const entry:
|
|
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
|
-
|
|
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 {
|