@every-env/compound-plugin 0.7.0 → 0.9.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 (40) hide show
  1. package/.cursor-plugin/marketplace.json +25 -0
  2. package/CHANGELOG.md +21 -0
  3. package/README.md +18 -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/docs/specs/kiro.md +171 -0
  10. package/package.json +1 -1
  11. package/plugins/coding-tutor/.cursor-plugin/plugin.json +21 -0
  12. package/plugins/compound-engineering/.claude-plugin/plugin.json +1 -1
  13. package/plugins/compound-engineering/.cursor-plugin/plugin.json +31 -0
  14. package/plugins/compound-engineering/.mcp.json +8 -0
  15. package/plugins/compound-engineering/CHANGELOG.md +10 -0
  16. package/plugins/compound-engineering/commands/lfg.md +3 -3
  17. package/plugins/compound-engineering/commands/slfg.md +2 -2
  18. package/plugins/compound-engineering/commands/workflows/plan.md +15 -1
  19. package/src/commands/convert.ts +2 -1
  20. package/src/commands/install.ts +9 -1
  21. package/src/commands/sync.ts +8 -8
  22. package/src/converters/{claude-to-cursor.ts → claude-to-copilot.ts} +93 -49
  23. package/src/converters/claude-to-kiro.ts +262 -0
  24. package/src/sync/{cursor.ts → copilot.ts} +36 -14
  25. package/src/targets/copilot.ts +48 -0
  26. package/src/targets/index.ts +18 -9
  27. package/src/targets/kiro.ts +122 -0
  28. package/src/types/copilot.ts +31 -0
  29. package/src/types/kiro.ts +44 -0
  30. package/src/utils/frontmatter.ts +1 -1
  31. package/tests/copilot-converter.test.ts +467 -0
  32. package/tests/copilot-writer.test.ts +189 -0
  33. package/tests/kiro-converter.test.ts +381 -0
  34. package/tests/kiro-writer.test.ts +273 -0
  35. package/tests/sync-copilot.test.ts +148 -0
  36. package/src/targets/cursor.ts +0 -48
  37. package/src/types/cursor.ts +0 -29
  38. package/tests/cursor-converter.test.ts +0 -347
  39. package/tests/cursor-writer.test.ts +0 -137
  40. package/tests/sync-cursor.test.ts +0 -92
@@ -2,21 +2,24 @@ import type { ClaudePlugin } from "../types/claude"
2
2
  import type { OpenCodeBundle } from "../types/opencode"
3
3
  import type { CodexBundle } from "../types/codex"
4
4
  import type { DroidBundle } from "../types/droid"
5
- import type { CursorBundle } from "../types/cursor"
6
5
  import type { PiBundle } from "../types/pi"
6
+ import type { CopilotBundle } from "../types/copilot"
7
7
  import type { GeminiBundle } from "../types/gemini"
8
+ import type { KiroBundle } from "../types/kiro"
8
9
  import { convertClaudeToOpenCode, type ClaudeToOpenCodeOptions } from "../converters/claude-to-opencode"
9
10
  import { convertClaudeToCodex } from "../converters/claude-to-codex"
10
11
  import { convertClaudeToDroid } from "../converters/claude-to-droid"
11
- import { convertClaudeToCursor } from "../converters/claude-to-cursor"
12
12
  import { convertClaudeToPi } from "../converters/claude-to-pi"
13
+ import { convertClaudeToCopilot } from "../converters/claude-to-copilot"
13
14
  import { convertClaudeToGemini } from "../converters/claude-to-gemini"
15
+ import { convertClaudeToKiro } from "../converters/claude-to-kiro"
14
16
  import { writeOpenCodeBundle } from "./opencode"
15
17
  import { writeCodexBundle } from "./codex"
16
18
  import { writeDroidBundle } from "./droid"
17
- import { writeCursorBundle } from "./cursor"
18
19
  import { writePiBundle } from "./pi"
20
+ import { writeCopilotBundle } from "./copilot"
19
21
  import { writeGeminiBundle } from "./gemini"
22
+ import { writeKiroBundle } from "./kiro"
20
23
 
21
24
  export type TargetHandler<TBundle = unknown> = {
22
25
  name: string
@@ -44,22 +47,28 @@ export const targets: Record<string, TargetHandler> = {
44
47
  convert: convertClaudeToDroid as TargetHandler<DroidBundle>["convert"],
45
48
  write: writeDroidBundle as TargetHandler<DroidBundle>["write"],
46
49
  },
47
- cursor: {
48
- name: "cursor",
49
- implemented: true,
50
- convert: convertClaudeToCursor as TargetHandler<CursorBundle>["convert"],
51
- write: writeCursorBundle as TargetHandler<CursorBundle>["write"],
52
- },
53
50
  pi: {
54
51
  name: "pi",
55
52
  implemented: true,
56
53
  convert: convertClaudeToPi as TargetHandler<PiBundle>["convert"],
57
54
  write: writePiBundle as TargetHandler<PiBundle>["write"],
58
55
  },
56
+ copilot: {
57
+ name: "copilot",
58
+ implemented: true,
59
+ convert: convertClaudeToCopilot as TargetHandler<CopilotBundle>["convert"],
60
+ write: writeCopilotBundle as TargetHandler<CopilotBundle>["write"],
61
+ },
59
62
  gemini: {
60
63
  name: "gemini",
61
64
  implemented: true,
62
65
  convert: convertClaudeToGemini as TargetHandler<GeminiBundle>["convert"],
63
66
  write: writeGeminiBundle as TargetHandler<GeminiBundle>["write"],
64
67
  },
68
+ kiro: {
69
+ name: "kiro",
70
+ implemented: true,
71
+ convert: convertClaudeToKiro as TargetHandler<KiroBundle>["convert"],
72
+ write: writeKiroBundle as TargetHandler<KiroBundle>["write"],
73
+ },
65
74
  }
@@ -0,0 +1,122 @@
1
+ import path from "path"
2
+ import { backupFile, copyDir, ensureDir, pathExists, readJson, writeJson, writeText } from "../utils/files"
3
+ import type { KiroBundle } from "../types/kiro"
4
+
5
+ export async function writeKiroBundle(outputRoot: string, bundle: KiroBundle): Promise<void> {
6
+ const paths = resolveKiroPaths(outputRoot)
7
+ await ensureDir(paths.kiroDir)
8
+
9
+ // Write agents
10
+ if (bundle.agents.length > 0) {
11
+ for (const agent of bundle.agents) {
12
+ // Validate name doesn't escape agents directory
13
+ validatePathSafe(agent.name, "agent")
14
+
15
+ // Write agent JSON config
16
+ await writeJson(
17
+ path.join(paths.agentsDir, `${agent.name}.json`),
18
+ agent.config,
19
+ )
20
+
21
+ // Write agent prompt file
22
+ await writeText(
23
+ path.join(paths.agentsDir, "prompts", `${agent.name}.md`),
24
+ agent.promptContent + "\n",
25
+ )
26
+ }
27
+ }
28
+
29
+ // Write generated skills (from commands)
30
+ if (bundle.generatedSkills.length > 0) {
31
+ for (const skill of bundle.generatedSkills) {
32
+ validatePathSafe(skill.name, "skill")
33
+ await writeText(
34
+ path.join(paths.skillsDir, skill.name, "SKILL.md"),
35
+ skill.content + "\n",
36
+ )
37
+ }
38
+ }
39
+
40
+ // Copy skill directories (pass-through)
41
+ if (bundle.skillDirs.length > 0) {
42
+ for (const skill of bundle.skillDirs) {
43
+ validatePathSafe(skill.name, "skill directory")
44
+ const destDir = path.join(paths.skillsDir, skill.name)
45
+
46
+ // Validate destination doesn't escape skills directory
47
+ const resolvedDest = path.resolve(destDir)
48
+ if (!resolvedDest.startsWith(path.resolve(paths.skillsDir))) {
49
+ console.warn(`Warning: Skill name "${skill.name}" escapes .kiro/skills/. Skipping.`)
50
+ continue
51
+ }
52
+
53
+ await copyDir(skill.sourceDir, destDir)
54
+ }
55
+ }
56
+
57
+ // Write steering files
58
+ if (bundle.steeringFiles.length > 0) {
59
+ for (const file of bundle.steeringFiles) {
60
+ validatePathSafe(file.name, "steering file")
61
+ await writeText(
62
+ path.join(paths.steeringDir, `${file.name}.md`),
63
+ file.content + "\n",
64
+ )
65
+ }
66
+ }
67
+
68
+ // Write MCP servers to mcp.json
69
+ if (Object.keys(bundle.mcpServers).length > 0) {
70
+ const mcpPath = path.join(paths.settingsDir, "mcp.json")
71
+ const backupPath = await backupFile(mcpPath)
72
+ if (backupPath) {
73
+ console.log(`Backed up existing mcp.json to ${backupPath}`)
74
+ }
75
+
76
+ // Merge with existing mcp.json if present
77
+ let existingConfig: Record<string, unknown> = {}
78
+ if (await pathExists(mcpPath)) {
79
+ try {
80
+ existingConfig = await readJson<Record<string, unknown>>(mcpPath)
81
+ } catch {
82
+ console.warn("Warning: existing mcp.json could not be parsed and will be replaced.")
83
+ }
84
+ }
85
+
86
+ const existingServers =
87
+ existingConfig.mcpServers && typeof existingConfig.mcpServers === "object"
88
+ ? (existingConfig.mcpServers as Record<string, unknown>)
89
+ : {}
90
+ const merged = { ...existingConfig, mcpServers: { ...existingServers, ...bundle.mcpServers } }
91
+ await writeJson(mcpPath, merged)
92
+ }
93
+ }
94
+
95
+ function resolveKiroPaths(outputRoot: string) {
96
+ const base = path.basename(outputRoot)
97
+ // If already pointing at .kiro, write directly into it
98
+ if (base === ".kiro") {
99
+ return {
100
+ kiroDir: outputRoot,
101
+ agentsDir: path.join(outputRoot, "agents"),
102
+ skillsDir: path.join(outputRoot, "skills"),
103
+ steeringDir: path.join(outputRoot, "steering"),
104
+ settingsDir: path.join(outputRoot, "settings"),
105
+ }
106
+ }
107
+ // Otherwise nest under .kiro
108
+ const kiroDir = path.join(outputRoot, ".kiro")
109
+ return {
110
+ kiroDir,
111
+ agentsDir: path.join(kiroDir, "agents"),
112
+ skillsDir: path.join(kiroDir, "skills"),
113
+ steeringDir: path.join(kiroDir, "steering"),
114
+ settingsDir: path.join(kiroDir, "settings"),
115
+ }
116
+ }
117
+
118
+ function validatePathSafe(name: string, label: string): void {
119
+ if (name.includes("..") || name.includes("/") || name.includes("\\")) {
120
+ throw new Error(`${label} name contains unsafe path characters: ${name}`)
121
+ }
122
+ }
@@ -0,0 +1,31 @@
1
+ export type CopilotAgent = {
2
+ name: string
3
+ content: string
4
+ }
5
+
6
+ export type CopilotGeneratedSkill = {
7
+ name: string
8
+ content: string
9
+ }
10
+
11
+ export type CopilotSkillDir = {
12
+ name: string
13
+ sourceDir: string
14
+ }
15
+
16
+ export type CopilotMcpServer = {
17
+ type: string
18
+ command?: string
19
+ args?: string[]
20
+ url?: string
21
+ tools: string[]
22
+ env?: Record<string, string>
23
+ headers?: Record<string, string>
24
+ }
25
+
26
+ export type CopilotBundle = {
27
+ agents: CopilotAgent[]
28
+ generatedSkills: CopilotGeneratedSkill[]
29
+ skillDirs: CopilotSkillDir[]
30
+ mcpConfig?: Record<string, CopilotMcpServer>
31
+ }
@@ -0,0 +1,44 @@
1
+ export type KiroAgent = {
2
+ name: string
3
+ config: KiroAgentConfig
4
+ promptContent: string
5
+ }
6
+
7
+ export type KiroAgentConfig = {
8
+ name: string
9
+ description: string
10
+ prompt: `file://${string}`
11
+ tools: ["*"]
12
+ resources: string[]
13
+ includeMcpJson: true
14
+ welcomeMessage?: string
15
+ }
16
+
17
+ export type KiroSkill = {
18
+ name: string
19
+ content: string // Full SKILL.md with YAML frontmatter
20
+ }
21
+
22
+ export type KiroSkillDir = {
23
+ name: string
24
+ sourceDir: string
25
+ }
26
+
27
+ export type KiroSteeringFile = {
28
+ name: string
29
+ content: string
30
+ }
31
+
32
+ export type KiroMcpServer = {
33
+ command: string
34
+ args?: string[]
35
+ env?: Record<string, string>
36
+ }
37
+
38
+ export type KiroBundle = {
39
+ agents: KiroAgent[]
40
+ generatedSkills: KiroSkill[]
41
+ skillDirs: KiroSkillDir[]
42
+ steeringFiles: KiroSteeringFile[]
43
+ mcpServers: Record<string, KiroMcpServer>
44
+ }
@@ -58,7 +58,7 @@ function formatYamlValue(value: unknown): string {
58
58
  if (raw.includes("\n")) {
59
59
  return `|\n${raw.split("\n").map((line) => ` ${line}`).join("\n")}`
60
60
  }
61
- if (raw.includes(":") || raw.startsWith("[") || raw.startsWith("{")) {
61
+ if (raw.includes(":") || raw.startsWith("[") || raw.startsWith("{") || raw === "*") {
62
62
  return JSON.stringify(raw)
63
63
  }
64
64
  return raw