@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.
- package/.claude-plugin/marketplace.json +3 -3
- package/AGENTS.md +5 -1
- package/CHANGELOG.md +50 -0
- package/CLAUDE.md +3 -3
- package/README.md +52 -14
- package/docs/plans/2026-02-14-feat-auto-detect-install-and-gemini-sync-plan.md +360 -0
- package/docs/plans/2026-02-25-feat-windsurf-global-scope-support-plan.md +627 -0
- package/docs/plans/2026-03-01-feat-ce-command-aliases-backwards-compatible-deprecation-plan.md +261 -0
- package/docs/plans/feature_opencode-commands-as-md-and-config-merge.md +574 -0
- package/docs/solutions/adding-converter-target-providers.md +692 -0
- package/docs/solutions/plugin-versioning-requirements.md +3 -3
- package/docs/specs/kiro.md +171 -0
- package/docs/specs/windsurf.md +477 -0
- package/package.json +1 -1
- package/plans/landing-page-launchkit-refresh.md +2 -2
- package/plugins/compound-engineering/.claude-plugin/plugin.json +2 -2
- package/plugins/compound-engineering/CHANGELOG.md +72 -1
- package/plugins/compound-engineering/CLAUDE.md +9 -7
- package/plugins/compound-engineering/README.md +10 -7
- package/plugins/compound-engineering/agents/research/git-history-analyzer.md +1 -1
- package/plugins/compound-engineering/agents/research/learnings-researcher.md +1 -1
- package/plugins/compound-engineering/agents/review/code-simplicity-reviewer.md +1 -1
- package/plugins/compound-engineering/commands/ce/brainstorm.md +145 -0
- package/plugins/compound-engineering/commands/ce/compound.md +240 -0
- package/plugins/compound-engineering/commands/ce/plan.md +636 -0
- package/plugins/compound-engineering/commands/ce/review.md +525 -0
- package/plugins/compound-engineering/commands/ce/work.md +470 -0
- package/plugins/compound-engineering/commands/create-agent-skill.md +1 -1
- package/plugins/compound-engineering/commands/deepen-plan.md +6 -6
- package/plugins/compound-engineering/commands/deploy-docs.md +1 -1
- package/plugins/compound-engineering/commands/feature-video.md +15 -6
- package/plugins/compound-engineering/commands/heal-skill.md +1 -1
- package/plugins/compound-engineering/commands/lfg.md +3 -3
- package/plugins/compound-engineering/commands/slfg.md +3 -3
- package/plugins/compound-engineering/commands/test-xcode.md +2 -2
- package/plugins/compound-engineering/commands/workflows/brainstorm.md +4 -123
- package/plugins/compound-engineering/commands/workflows/compound.md +4 -234
- package/plugins/compound-engineering/commands/workflows/plan.md +4 -562
- package/plugins/compound-engineering/commands/workflows/review.md +4 -522
- package/plugins/compound-engineering/commands/workflows/work.md +4 -448
- package/plugins/compound-engineering/skills/brainstorming/SKILL.md +3 -3
- package/plugins/compound-engineering/skills/document-review/SKILL.md +1 -1
- package/plugins/compound-engineering/skills/file-todos/SKILL.md +1 -1
- package/plugins/compound-engineering/skills/git-worktree/SKILL.md +5 -5
- package/plugins/compound-engineering/skills/proof/SKILL.md +185 -0
- package/plugins/compound-engineering/skills/resolve-pr-parallel/SKILL.md +1 -1
- package/plugins/compound-engineering/skills/setup/SKILL.md +2 -2
- package/src/commands/convert.ts +101 -23
- package/src/commands/install.ts +102 -41
- package/src/commands/sync.ts +58 -38
- package/src/converters/claude-to-kiro.ts +262 -0
- package/src/converters/claude-to-openclaw.ts +240 -0
- package/src/converters/claude-to-opencode.ts +12 -10
- package/src/converters/claude-to-qwen.ts +238 -0
- package/src/converters/claude-to-windsurf.ts +205 -0
- package/src/sync/gemini.ts +76 -0
- package/src/targets/index.ts +69 -1
- package/src/targets/kiro.ts +122 -0
- package/src/targets/openclaw.ts +96 -0
- package/src/targets/opencode.ts +76 -10
- package/src/targets/qwen.ts +64 -0
- package/src/targets/windsurf.ts +104 -0
- package/src/types/kiro.ts +44 -0
- package/src/types/openclaw.ts +52 -0
- package/src/types/opencode.ts +7 -8
- package/src/types/qwen.ts +48 -0
- package/src/types/windsurf.ts +34 -0
- package/src/utils/detect-tools.ts +46 -0
- package/src/utils/files.ts +7 -0
- package/src/utils/resolve-output.ts +50 -0
- package/src/utils/secrets.ts +24 -0
- package/tests/cli.test.ts +78 -0
- package/tests/converter.test.ts +43 -10
- package/tests/detect-tools.test.ts +96 -0
- package/tests/kiro-converter.test.ts +381 -0
- package/tests/kiro-writer.test.ts +273 -0
- package/tests/openclaw-converter.test.ts +200 -0
- package/tests/opencode-writer.test.ts +142 -5
- package/tests/qwen-converter.test.ts +238 -0
- package/tests/resolve-output.test.ts +131 -0
- package/tests/sync-gemini.test.ts +106 -0
- package/tests/windsurf-converter.test.ts +573 -0
- package/tests/windsurf-writer.test.ts +359 -0
- package/docs/css/docs.css +0 -675
- package/docs/css/style.css +0 -2886
- package/docs/index.html +0 -1046
- package/docs/js/main.js +0 -225
- package/docs/pages/agents.html +0 -649
- package/docs/pages/changelog.html +0 -534
- package/docs/pages/commands.html +0 -523
- package/docs/pages/getting-started.html +0 -582
- package/docs/pages/mcp-servers.html +0 -409
- 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
|
+
})
|