@every-env/compound-plugin 0.8.0 → 0.12.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 (93) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/AGENTS.md +5 -1
  3. package/CHANGELOG.md +50 -0
  4. package/CLAUDE.md +3 -3
  5. package/README.md +52 -14
  6. package/docs/plans/2026-02-14-feat-auto-detect-install-and-gemini-sync-plan.md +360 -0
  7. package/docs/plans/2026-02-25-feat-windsurf-global-scope-support-plan.md +627 -0
  8. package/docs/plans/2026-03-01-feat-ce-command-aliases-backwards-compatible-deprecation-plan.md +261 -0
  9. package/docs/plans/feature_opencode-commands-as-md-and-config-merge.md +574 -0
  10. package/docs/solutions/adding-converter-target-providers.md +692 -0
  11. package/docs/solutions/plugin-versioning-requirements.md +3 -3
  12. package/docs/specs/kiro.md +171 -0
  13. package/docs/specs/windsurf.md +477 -0
  14. package/package.json +1 -1
  15. package/plans/landing-page-launchkit-refresh.md +2 -2
  16. package/plugins/compound-engineering/.claude-plugin/plugin.json +2 -2
  17. package/plugins/compound-engineering/CHANGELOG.md +72 -1
  18. package/plugins/compound-engineering/CLAUDE.md +9 -7
  19. package/plugins/compound-engineering/README.md +10 -7
  20. package/plugins/compound-engineering/agents/research/git-history-analyzer.md +1 -1
  21. package/plugins/compound-engineering/agents/research/learnings-researcher.md +1 -1
  22. package/plugins/compound-engineering/agents/review/code-simplicity-reviewer.md +1 -1
  23. package/plugins/compound-engineering/commands/ce/brainstorm.md +145 -0
  24. package/plugins/compound-engineering/commands/ce/compound.md +240 -0
  25. package/plugins/compound-engineering/commands/ce/plan.md +636 -0
  26. package/plugins/compound-engineering/commands/ce/review.md +525 -0
  27. package/plugins/compound-engineering/commands/ce/work.md +470 -0
  28. package/plugins/compound-engineering/commands/create-agent-skill.md +1 -1
  29. package/plugins/compound-engineering/commands/deepen-plan.md +6 -6
  30. package/plugins/compound-engineering/commands/deploy-docs.md +1 -1
  31. package/plugins/compound-engineering/commands/feature-video.md +15 -6
  32. package/plugins/compound-engineering/commands/heal-skill.md +1 -1
  33. package/plugins/compound-engineering/commands/lfg.md +3 -3
  34. package/plugins/compound-engineering/commands/slfg.md +3 -3
  35. package/plugins/compound-engineering/commands/test-xcode.md +2 -2
  36. package/plugins/compound-engineering/commands/workflows/brainstorm.md +4 -123
  37. package/plugins/compound-engineering/commands/workflows/compound.md +4 -234
  38. package/plugins/compound-engineering/commands/workflows/plan.md +4 -562
  39. package/plugins/compound-engineering/commands/workflows/review.md +4 -522
  40. package/plugins/compound-engineering/commands/workflows/work.md +4 -448
  41. package/plugins/compound-engineering/skills/brainstorming/SKILL.md +3 -3
  42. package/plugins/compound-engineering/skills/document-review/SKILL.md +1 -1
  43. package/plugins/compound-engineering/skills/file-todos/SKILL.md +1 -1
  44. package/plugins/compound-engineering/skills/git-worktree/SKILL.md +5 -5
  45. package/plugins/compound-engineering/skills/proof/SKILL.md +185 -0
  46. package/plugins/compound-engineering/skills/resolve-pr-parallel/SKILL.md +1 -1
  47. package/plugins/compound-engineering/skills/setup/SKILL.md +2 -2
  48. package/src/commands/convert.ts +101 -23
  49. package/src/commands/install.ts +102 -41
  50. package/src/commands/sync.ts +58 -38
  51. package/src/converters/claude-to-kiro.ts +262 -0
  52. package/src/converters/claude-to-openclaw.ts +240 -0
  53. package/src/converters/claude-to-opencode.ts +12 -10
  54. package/src/converters/claude-to-qwen.ts +238 -0
  55. package/src/converters/claude-to-windsurf.ts +205 -0
  56. package/src/sync/gemini.ts +76 -0
  57. package/src/targets/index.ts +69 -1
  58. package/src/targets/kiro.ts +122 -0
  59. package/src/targets/openclaw.ts +96 -0
  60. package/src/targets/opencode.ts +76 -10
  61. package/src/targets/qwen.ts +64 -0
  62. package/src/targets/windsurf.ts +104 -0
  63. package/src/types/kiro.ts +44 -0
  64. package/src/types/openclaw.ts +52 -0
  65. package/src/types/opencode.ts +7 -8
  66. package/src/types/qwen.ts +48 -0
  67. package/src/types/windsurf.ts +34 -0
  68. package/src/utils/detect-tools.ts +46 -0
  69. package/src/utils/files.ts +7 -0
  70. package/src/utils/resolve-output.ts +50 -0
  71. package/src/utils/secrets.ts +24 -0
  72. package/tests/cli.test.ts +78 -0
  73. package/tests/converter.test.ts +43 -10
  74. package/tests/detect-tools.test.ts +96 -0
  75. package/tests/kiro-converter.test.ts +381 -0
  76. package/tests/kiro-writer.test.ts +273 -0
  77. package/tests/openclaw-converter.test.ts +200 -0
  78. package/tests/opencode-writer.test.ts +142 -5
  79. package/tests/qwen-converter.test.ts +238 -0
  80. package/tests/resolve-output.test.ts +131 -0
  81. package/tests/sync-gemini.test.ts +106 -0
  82. package/tests/windsurf-converter.test.ts +573 -0
  83. package/tests/windsurf-writer.test.ts +359 -0
  84. package/docs/css/docs.css +0 -675
  85. package/docs/css/style.css +0 -2886
  86. package/docs/index.html +0 -1046
  87. package/docs/js/main.js +0 -225
  88. package/docs/pages/agents.html +0 -649
  89. package/docs/pages/changelog.html +0 -534
  90. package/docs/pages/commands.html +0 -523
  91. package/docs/pages/getting-started.html +0 -582
  92. package/docs/pages/mcp-servers.html +0 -409
  93. package/docs/pages/skills.html +0 -611
@@ -0,0 +1,131 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import os from "os"
3
+ import path from "path"
4
+ import { resolveTargetOutputRoot } from "../src/utils/resolve-output"
5
+
6
+ const baseOptions = {
7
+ outputRoot: "/tmp/output",
8
+ codexHome: path.join(os.homedir(), ".codex"),
9
+ piHome: path.join(os.homedir(), ".pi", "agent"),
10
+ hasExplicitOutput: false,
11
+ }
12
+
13
+ describe("resolveTargetOutputRoot", () => {
14
+ test("codex returns codexHome", () => {
15
+ const result = resolveTargetOutputRoot({ ...baseOptions, targetName: "codex" })
16
+ expect(result).toBe(baseOptions.codexHome)
17
+ })
18
+
19
+ test("pi returns piHome", () => {
20
+ const result = resolveTargetOutputRoot({ ...baseOptions, targetName: "pi" })
21
+ expect(result).toBe(baseOptions.piHome)
22
+ })
23
+
24
+ test("droid returns ~/.factory", () => {
25
+ const result = resolveTargetOutputRoot({ ...baseOptions, targetName: "droid" })
26
+ expect(result).toBe(path.join(os.homedir(), ".factory"))
27
+ })
28
+
29
+ test("cursor with no explicit output uses cwd", () => {
30
+ const result = resolveTargetOutputRoot({ ...baseOptions, targetName: "cursor" })
31
+ expect(result).toBe(path.join(process.cwd(), ".cursor"))
32
+ })
33
+
34
+ test("cursor with explicit output uses outputRoot", () => {
35
+ const result = resolveTargetOutputRoot({
36
+ ...baseOptions,
37
+ targetName: "cursor",
38
+ hasExplicitOutput: true,
39
+ })
40
+ expect(result).toBe(path.join("/tmp/output", ".cursor"))
41
+ })
42
+
43
+ test("windsurf default scope (global) resolves to ~/.codeium/windsurf/", () => {
44
+ const result = resolveTargetOutputRoot({
45
+ ...baseOptions,
46
+ targetName: "windsurf",
47
+ scope: "global",
48
+ })
49
+ expect(result).toBe(path.join(os.homedir(), ".codeium", "windsurf"))
50
+ })
51
+
52
+ test("windsurf workspace scope resolves to cwd/.windsurf/", () => {
53
+ const result = resolveTargetOutputRoot({
54
+ ...baseOptions,
55
+ targetName: "windsurf",
56
+ scope: "workspace",
57
+ })
58
+ expect(result).toBe(path.join(process.cwd(), ".windsurf"))
59
+ })
60
+
61
+ test("windsurf with explicit output overrides global scope", () => {
62
+ const result = resolveTargetOutputRoot({
63
+ ...baseOptions,
64
+ targetName: "windsurf",
65
+ hasExplicitOutput: true,
66
+ scope: "global",
67
+ })
68
+ expect(result).toBe("/tmp/output")
69
+ })
70
+
71
+ test("windsurf with explicit output overrides workspace scope", () => {
72
+ const result = resolveTargetOutputRoot({
73
+ ...baseOptions,
74
+ targetName: "windsurf",
75
+ hasExplicitOutput: true,
76
+ scope: "workspace",
77
+ })
78
+ expect(result).toBe("/tmp/output")
79
+ })
80
+
81
+ test("windsurf with no scope and no explicit output uses cwd/.windsurf/", () => {
82
+ const result = resolveTargetOutputRoot({
83
+ ...baseOptions,
84
+ targetName: "windsurf",
85
+ })
86
+ expect(result).toBe(path.join(process.cwd(), ".windsurf"))
87
+ })
88
+
89
+ test("opencode returns outputRoot as-is", () => {
90
+ const result = resolveTargetOutputRoot({ ...baseOptions, targetName: "opencode" })
91
+ expect(result).toBe("/tmp/output")
92
+ })
93
+
94
+ test("openclaw uses openclawHome + pluginName", () => {
95
+ const result = resolveTargetOutputRoot({
96
+ ...baseOptions,
97
+ targetName: "openclaw",
98
+ openclawHome: "/custom/openclaw/extensions",
99
+ pluginName: "my-plugin",
100
+ })
101
+ expect(result).toBe("/custom/openclaw/extensions/my-plugin")
102
+ })
103
+
104
+ test("openclaw falls back to default home when not provided", () => {
105
+ const result = resolveTargetOutputRoot({
106
+ ...baseOptions,
107
+ targetName: "openclaw",
108
+ pluginName: "my-plugin",
109
+ })
110
+ expect(result).toBe(path.join(os.homedir(), ".openclaw", "extensions", "my-plugin"))
111
+ })
112
+
113
+ test("qwen uses qwenHome + pluginName", () => {
114
+ const result = resolveTargetOutputRoot({
115
+ ...baseOptions,
116
+ targetName: "qwen",
117
+ qwenHome: "/custom/qwen/extensions",
118
+ pluginName: "my-plugin",
119
+ })
120
+ expect(result).toBe("/custom/qwen/extensions/my-plugin")
121
+ })
122
+
123
+ test("qwen falls back to default home when not provided", () => {
124
+ const result = resolveTargetOutputRoot({
125
+ ...baseOptions,
126
+ targetName: "qwen",
127
+ pluginName: "my-plugin",
128
+ })
129
+ expect(result).toBe(path.join(os.homedir(), ".qwen", "extensions", "my-plugin"))
130
+ })
131
+ })
@@ -0,0 +1,106 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { promises as fs } from "fs"
3
+ import path from "path"
4
+ import os from "os"
5
+ import { syncToGemini } from "../src/sync/gemini"
6
+ import type { ClaudeHomeConfig } from "../src/parsers/claude-home"
7
+
8
+ describe("syncToGemini", () => {
9
+ test("symlinks skills and writes settings.json", async () => {
10
+ const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "sync-gemini-"))
11
+ const fixtureSkillDir = path.join(import.meta.dir, "fixtures", "sample-plugin", "skills", "skill-one")
12
+
13
+ const config: ClaudeHomeConfig = {
14
+ skills: [
15
+ {
16
+ name: "skill-one",
17
+ sourceDir: fixtureSkillDir,
18
+ skillPath: path.join(fixtureSkillDir, "SKILL.md"),
19
+ },
20
+ ],
21
+ mcpServers: {
22
+ context7: { url: "https://mcp.context7.com/mcp" },
23
+ local: { command: "echo", args: ["hello"], env: { FOO: "bar" } },
24
+ },
25
+ }
26
+
27
+ await syncToGemini(config, tempRoot)
28
+
29
+ // Check skill symlink
30
+ const linkedSkillPath = path.join(tempRoot, "skills", "skill-one")
31
+ const linkedStat = await fs.lstat(linkedSkillPath)
32
+ expect(linkedStat.isSymbolicLink()).toBe(true)
33
+
34
+ // Check settings.json
35
+ const settingsPath = path.join(tempRoot, "settings.json")
36
+ const settings = JSON.parse(await fs.readFile(settingsPath, "utf8")) as {
37
+ mcpServers: Record<string, { url?: string; command?: string; args?: string[]; env?: Record<string, string> }>
38
+ }
39
+
40
+ expect(settings.mcpServers.context7?.url).toBe("https://mcp.context7.com/mcp")
41
+ expect(settings.mcpServers.local?.command).toBe("echo")
42
+ expect(settings.mcpServers.local?.args).toEqual(["hello"])
43
+ expect(settings.mcpServers.local?.env).toEqual({ FOO: "bar" })
44
+ })
45
+
46
+ test("merges existing settings.json", async () => {
47
+ const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "sync-gemini-merge-"))
48
+ const settingsPath = path.join(tempRoot, "settings.json")
49
+
50
+ await fs.writeFile(
51
+ settingsPath,
52
+ JSON.stringify({
53
+ theme: "dark",
54
+ mcpServers: { existing: { command: "node", args: ["server.js"] } },
55
+ }, null, 2),
56
+ )
57
+
58
+ const config: ClaudeHomeConfig = {
59
+ skills: [],
60
+ mcpServers: {
61
+ context7: { url: "https://mcp.context7.com/mcp" },
62
+ },
63
+ }
64
+
65
+ await syncToGemini(config, tempRoot)
66
+
67
+ const merged = JSON.parse(await fs.readFile(settingsPath, "utf8")) as {
68
+ theme: string
69
+ mcpServers: Record<string, { command?: string; url?: string }>
70
+ }
71
+
72
+ // Preserves existing settings
73
+ expect(merged.theme).toBe("dark")
74
+ // Preserves existing MCP servers
75
+ expect(merged.mcpServers.existing?.command).toBe("node")
76
+ // Adds new MCP servers
77
+ expect(merged.mcpServers.context7?.url).toBe("https://mcp.context7.com/mcp")
78
+ })
79
+
80
+ test("does not write settings.json when no MCP servers", async () => {
81
+ const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "sync-gemini-nomcp-"))
82
+ const fixtureSkillDir = path.join(import.meta.dir, "fixtures", "sample-plugin", "skills", "skill-one")
83
+
84
+ const config: ClaudeHomeConfig = {
85
+ skills: [
86
+ {
87
+ name: "skill-one",
88
+ sourceDir: fixtureSkillDir,
89
+ skillPath: path.join(fixtureSkillDir, "SKILL.md"),
90
+ },
91
+ ],
92
+ mcpServers: {},
93
+ }
94
+
95
+ await syncToGemini(config, tempRoot)
96
+
97
+ // Skills should still be symlinked
98
+ const linkedSkillPath = path.join(tempRoot, "skills", "skill-one")
99
+ const linkedStat = await fs.lstat(linkedSkillPath)
100
+ expect(linkedStat.isSymbolicLink()).toBe(true)
101
+
102
+ // But settings.json should not exist
103
+ const settingsExists = await fs.access(path.join(tempRoot, "settings.json")).then(() => true).catch(() => false)
104
+ expect(settingsExists).toBe(false)
105
+ })
106
+ })