@every-env/compound-plugin 0.3.0 → 0.5.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/{plugins/compound-engineering → .claude}/commands/release-docs.md +0 -1
- package/.claude-plugin/marketplace.json +2 -2
- package/.github/workflows/ci.yml +1 -1
- package/.github/workflows/deploy-docs.yml +3 -3
- package/.github/workflows/publish.yml +37 -0
- package/README.md +12 -3
- package/docs/index.html +13 -13
- package/docs/pages/changelog.html +39 -0
- package/docs/plans/2026-02-08-feat-convert-local-md-settings-for-opencode-codex-plan.md +143 -0
- package/docs/plans/2026-02-08-feat-simplify-plugin-settings-plan.md +195 -0
- package/docs/plans/2026-02-09-refactor-dspy-ruby-skill-update-plan.md +104 -0
- package/docs/plans/2026-02-12-feat-add-cursor-cli-target-provider-plan.md +306 -0
- package/docs/specs/cursor.md +85 -0
- package/package.json +1 -1
- package/plugins/compound-engineering/.claude-plugin/plugin.json +2 -2
- package/plugins/compound-engineering/CHANGELOG.md +38 -0
- package/plugins/compound-engineering/README.md +5 -3
- package/plugins/compound-engineering/commands/workflows/brainstorm.md +6 -1
- package/plugins/compound-engineering/commands/workflows/compound.md +1 -0
- package/plugins/compound-engineering/commands/workflows/review.md +23 -21
- package/plugins/compound-engineering/commands/workflows/work.md +29 -15
- package/plugins/compound-engineering/skills/dspy-ruby/SKILL.md +539 -396
- package/plugins/compound-engineering/skills/dspy-ruby/assets/config-template.rb +159 -331
- package/plugins/compound-engineering/skills/dspy-ruby/assets/module-template.rb +210 -236
- package/plugins/compound-engineering/skills/dspy-ruby/assets/signature-template.rb +173 -95
- package/plugins/compound-engineering/skills/dspy-ruby/references/core-concepts.md +552 -143
- package/plugins/compound-engineering/skills/dspy-ruby/references/observability.md +366 -0
- package/plugins/compound-engineering/skills/dspy-ruby/references/optimization.md +440 -460
- package/plugins/compound-engineering/skills/dspy-ruby/references/providers.md +305 -225
- package/plugins/compound-engineering/skills/dspy-ruby/references/toolsets.md +502 -0
- package/plugins/compound-engineering/skills/setup/SKILL.md +168 -0
- package/src/commands/convert.ts +10 -5
- package/src/commands/install.ts +10 -5
- package/src/converters/claude-to-codex.ts +7 -2
- package/src/converters/claude-to-cursor.ts +166 -0
- package/src/converters/claude-to-droid.ts +174 -0
- package/src/converters/claude-to-opencode.ts +8 -2
- package/src/targets/cursor.ts +48 -0
- package/src/targets/droid.ts +50 -0
- package/src/targets/index.ts +18 -0
- package/src/types/cursor.ts +29 -0
- package/src/types/droid.ts +20 -0
- package/tests/codex-converter.test.ts +62 -0
- package/tests/converter.test.ts +61 -0
- package/tests/cursor-converter.test.ts +347 -0
- package/tests/cursor-writer.test.ts +137 -0
- package/tests/droid-converter.test.ts +277 -0
- package/tests/droid-writer.test.ts +100 -0
- package/plugins/compound-engineering/commands/technical_review.md +0 -8
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { convertClaudeToDroid } from "../src/converters/claude-to-droid"
|
|
3
|
+
import { parseFrontmatter } from "../src/utils/frontmatter"
|
|
4
|
+
import type { ClaudePlugin } from "../src/types/claude"
|
|
5
|
+
|
|
6
|
+
const fixturePlugin: ClaudePlugin = {
|
|
7
|
+
root: "/tmp/plugin",
|
|
8
|
+
manifest: { name: "fixture", version: "1.0.0" },
|
|
9
|
+
agents: [
|
|
10
|
+
{
|
|
11
|
+
name: "Security Reviewer",
|
|
12
|
+
description: "Security-focused agent",
|
|
13
|
+
capabilities: ["Threat modeling", "OWASP"],
|
|
14
|
+
model: "claude-sonnet-4-20250514",
|
|
15
|
+
body: "Focus on vulnerabilities.",
|
|
16
|
+
sourcePath: "/tmp/plugin/agents/security-reviewer.md",
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
commands: [
|
|
20
|
+
{
|
|
21
|
+
name: "workflows:plan",
|
|
22
|
+
description: "Planning command",
|
|
23
|
+
argumentHint: "[FOCUS]",
|
|
24
|
+
model: "inherit",
|
|
25
|
+
allowedTools: ["Read"],
|
|
26
|
+
body: "Plan the work.",
|
|
27
|
+
sourcePath: "/tmp/plugin/commands/workflows/plan.md",
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
skills: [
|
|
31
|
+
{
|
|
32
|
+
name: "existing-skill",
|
|
33
|
+
description: "Existing skill",
|
|
34
|
+
sourceDir: "/tmp/plugin/skills/existing-skill",
|
|
35
|
+
skillPath: "/tmp/plugin/skills/existing-skill/SKILL.md",
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
hooks: undefined,
|
|
39
|
+
mcpServers: undefined,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe("convertClaudeToDroid", () => {
|
|
43
|
+
test("flattens namespaced command names", () => {
|
|
44
|
+
const bundle = convertClaudeToDroid(fixturePlugin, {
|
|
45
|
+
agentMode: "subagent",
|
|
46
|
+
inferTemperature: false,
|
|
47
|
+
permissions: "none",
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
expect(bundle.commands).toHaveLength(1)
|
|
51
|
+
const command = bundle.commands[0]
|
|
52
|
+
expect(command.name).toBe("plan")
|
|
53
|
+
|
|
54
|
+
const parsed = parseFrontmatter(command.content)
|
|
55
|
+
expect(parsed.data.description).toBe("Planning command")
|
|
56
|
+
expect(parsed.data["argument-hint"]).toBe("[FOCUS]")
|
|
57
|
+
expect(parsed.body).toContain("Plan the work.")
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test("converts agents to droids with frontmatter", () => {
|
|
61
|
+
const bundle = convertClaudeToDroid(fixturePlugin, {
|
|
62
|
+
agentMode: "subagent",
|
|
63
|
+
inferTemperature: false,
|
|
64
|
+
permissions: "none",
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
expect(bundle.droids).toHaveLength(1)
|
|
68
|
+
const droid = bundle.droids[0]
|
|
69
|
+
expect(droid.name).toBe("security-reviewer")
|
|
70
|
+
|
|
71
|
+
const parsed = parseFrontmatter(droid.content)
|
|
72
|
+
expect(parsed.data.name).toBe("security-reviewer")
|
|
73
|
+
expect(parsed.data.description).toBe("Security-focused agent")
|
|
74
|
+
expect(parsed.data.model).toBe("claude-sonnet-4-20250514")
|
|
75
|
+
expect(parsed.body).toContain("Capabilities")
|
|
76
|
+
expect(parsed.body).toContain("Threat modeling")
|
|
77
|
+
expect(parsed.body).toContain("Focus on vulnerabilities.")
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test("passes through skill directories", () => {
|
|
81
|
+
const bundle = convertClaudeToDroid(fixturePlugin, {
|
|
82
|
+
agentMode: "subagent",
|
|
83
|
+
inferTemperature: false,
|
|
84
|
+
permissions: "none",
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
expect(bundle.skillDirs).toHaveLength(1)
|
|
88
|
+
expect(bundle.skillDirs[0].name).toBe("existing-skill")
|
|
89
|
+
expect(bundle.skillDirs[0].sourceDir).toBe("/tmp/plugin/skills/existing-skill")
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test("sets model to inherit when not specified", () => {
|
|
93
|
+
const plugin: ClaudePlugin = {
|
|
94
|
+
...fixturePlugin,
|
|
95
|
+
agents: [
|
|
96
|
+
{
|
|
97
|
+
name: "basic-agent",
|
|
98
|
+
description: "Basic agent",
|
|
99
|
+
model: "inherit",
|
|
100
|
+
body: "Do things.",
|
|
101
|
+
sourcePath: "/tmp/plugin/agents/basic.md",
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const bundle = convertClaudeToDroid(plugin, {
|
|
107
|
+
agentMode: "subagent",
|
|
108
|
+
inferTemperature: false,
|
|
109
|
+
permissions: "none",
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const parsed = parseFrontmatter(bundle.droids[0].content)
|
|
113
|
+
expect(parsed.data.model).toBe("inherit")
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test("transforms Task agent calls to droid-compatible syntax", () => {
|
|
117
|
+
const plugin: ClaudePlugin = {
|
|
118
|
+
...fixturePlugin,
|
|
119
|
+
commands: [
|
|
120
|
+
{
|
|
121
|
+
name: "plan",
|
|
122
|
+
description: "Planning with agents",
|
|
123
|
+
body: `Run these agents in parallel:
|
|
124
|
+
|
|
125
|
+
- Task repo-research-analyst(feature_description)
|
|
126
|
+
- Task learnings-researcher(feature_description)
|
|
127
|
+
|
|
128
|
+
Then consolidate findings.
|
|
129
|
+
|
|
130
|
+
Task best-practices-researcher(topic)`,
|
|
131
|
+
sourcePath: "/tmp/plugin/commands/plan.md",
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
agents: [],
|
|
135
|
+
skills: [],
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const bundle = convertClaudeToDroid(plugin, {
|
|
139
|
+
agentMode: "subagent",
|
|
140
|
+
inferTemperature: false,
|
|
141
|
+
permissions: "none",
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
const parsed = parseFrontmatter(bundle.commands[0].content)
|
|
145
|
+
expect(parsed.body).toContain("Task repo-research-analyst: feature_description")
|
|
146
|
+
expect(parsed.body).toContain("Task learnings-researcher: feature_description")
|
|
147
|
+
expect(parsed.body).toContain("Task best-practices-researcher: topic")
|
|
148
|
+
expect(parsed.body).not.toContain("Task repo-research-analyst(")
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test("transforms slash commands by flattening namespaces", () => {
|
|
152
|
+
const plugin: ClaudePlugin = {
|
|
153
|
+
...fixturePlugin,
|
|
154
|
+
commands: [
|
|
155
|
+
{
|
|
156
|
+
name: "plan",
|
|
157
|
+
description: "Planning with commands",
|
|
158
|
+
body: `After planning, you can:
|
|
159
|
+
|
|
160
|
+
1. Run /deepen-plan to enhance
|
|
161
|
+
2. Run /plan_review for feedback
|
|
162
|
+
3. Start /workflows:work to implement
|
|
163
|
+
|
|
164
|
+
Don't confuse with file paths like /tmp/output.md or /dev/null.`,
|
|
165
|
+
sourcePath: "/tmp/plugin/commands/plan.md",
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
agents: [],
|
|
169
|
+
skills: [],
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const bundle = convertClaudeToDroid(plugin, {
|
|
173
|
+
agentMode: "subagent",
|
|
174
|
+
inferTemperature: false,
|
|
175
|
+
permissions: "none",
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
const parsed = parseFrontmatter(bundle.commands[0].content)
|
|
179
|
+
expect(parsed.body).toContain("/deepen-plan")
|
|
180
|
+
expect(parsed.body).toContain("/plan_review")
|
|
181
|
+
expect(parsed.body).toContain("/work")
|
|
182
|
+
expect(parsed.body).not.toContain("/workflows:work")
|
|
183
|
+
// File paths should NOT be transformed
|
|
184
|
+
expect(parsed.body).toContain("/tmp/output.md")
|
|
185
|
+
expect(parsed.body).toContain("/dev/null")
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
test("transforms @agent references to droid references", () => {
|
|
189
|
+
const plugin: ClaudePlugin = {
|
|
190
|
+
...fixturePlugin,
|
|
191
|
+
commands: [
|
|
192
|
+
{
|
|
193
|
+
name: "review",
|
|
194
|
+
description: "Review command",
|
|
195
|
+
body: "Have @agent-dhh-rails-reviewer and @agent-security-sentinel review the code.",
|
|
196
|
+
sourcePath: "/tmp/plugin/commands/review.md",
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
agents: [],
|
|
200
|
+
skills: [],
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const bundle = convertClaudeToDroid(plugin, {
|
|
204
|
+
agentMode: "subagent",
|
|
205
|
+
inferTemperature: false,
|
|
206
|
+
permissions: "none",
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
const parsed = parseFrontmatter(bundle.commands[0].content)
|
|
210
|
+
expect(parsed.body).toContain("the dhh-rails-reviewer droid")
|
|
211
|
+
expect(parsed.body).toContain("the security-sentinel droid")
|
|
212
|
+
expect(parsed.body).not.toContain("@agent-")
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
test("preserves disable-model-invocation on commands", () => {
|
|
216
|
+
const plugin: ClaudePlugin = {
|
|
217
|
+
...fixturePlugin,
|
|
218
|
+
commands: [
|
|
219
|
+
{
|
|
220
|
+
name: "disabled-cmd",
|
|
221
|
+
description: "Disabled command",
|
|
222
|
+
disableModelInvocation: true,
|
|
223
|
+
body: "Body.",
|
|
224
|
+
sourcePath: "/tmp/plugin/commands/disabled.md",
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
agents: [],
|
|
228
|
+
skills: [],
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const bundle = convertClaudeToDroid(plugin, {
|
|
232
|
+
agentMode: "subagent",
|
|
233
|
+
inferTemperature: false,
|
|
234
|
+
permissions: "none",
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
const parsed = parseFrontmatter(bundle.commands[0].content)
|
|
238
|
+
expect(parsed.data["disable-model-invocation"]).toBe(true)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
test("handles multiple commands including nested and top-level", () => {
|
|
242
|
+
const plugin: ClaudePlugin = {
|
|
243
|
+
...fixturePlugin,
|
|
244
|
+
commands: [
|
|
245
|
+
{
|
|
246
|
+
name: "workflows:plan",
|
|
247
|
+
description: "Plan",
|
|
248
|
+
body: "Plan body.",
|
|
249
|
+
sourcePath: "/tmp/plugin/commands/workflows/plan.md",
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: "workflows:work",
|
|
253
|
+
description: "Work",
|
|
254
|
+
body: "Work body.",
|
|
255
|
+
sourcePath: "/tmp/plugin/commands/workflows/work.md",
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: "changelog",
|
|
259
|
+
description: "Changelog",
|
|
260
|
+
body: "Changelog body.",
|
|
261
|
+
sourcePath: "/tmp/plugin/commands/changelog.md",
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
agents: [],
|
|
265
|
+
skills: [],
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const bundle = convertClaudeToDroid(plugin, {
|
|
269
|
+
agentMode: "subagent",
|
|
270
|
+
inferTemperature: false,
|
|
271
|
+
permissions: "none",
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
const names = bundle.commands.map((c) => c.name)
|
|
275
|
+
expect(names).toEqual(["plan", "work", "changelog"])
|
|
276
|
+
})
|
|
277
|
+
})
|
|
@@ -0,0 +1,100 @@
|
|
|
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 { writeDroidBundle } from "../src/targets/droid"
|
|
6
|
+
import type { DroidBundle } from "../src/types/droid"
|
|
7
|
+
|
|
8
|
+
async function exists(filePath: string): Promise<boolean> {
|
|
9
|
+
try {
|
|
10
|
+
await fs.access(filePath)
|
|
11
|
+
return true
|
|
12
|
+
} catch {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("writeDroidBundle", () => {
|
|
18
|
+
test("writes commands, droids, and skills", async () => {
|
|
19
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "droid-test-"))
|
|
20
|
+
const bundle: DroidBundle = {
|
|
21
|
+
commands: [{ name: "plan", content: "Plan command content" }],
|
|
22
|
+
droids: [{ name: "security-reviewer", content: "Droid content" }],
|
|
23
|
+
skillDirs: [
|
|
24
|
+
{
|
|
25
|
+
name: "skill-one",
|
|
26
|
+
sourceDir: path.join(import.meta.dir, "fixtures", "sample-plugin", "skills", "skill-one"),
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await writeDroidBundle(tempRoot, bundle)
|
|
32
|
+
|
|
33
|
+
expect(await exists(path.join(tempRoot, ".factory", "commands", "plan.md"))).toBe(true)
|
|
34
|
+
expect(await exists(path.join(tempRoot, ".factory", "droids", "security-reviewer.md"))).toBe(true)
|
|
35
|
+
expect(await exists(path.join(tempRoot, ".factory", "skills", "skill-one", "SKILL.md"))).toBe(true)
|
|
36
|
+
|
|
37
|
+
const commandContent = await fs.readFile(
|
|
38
|
+
path.join(tempRoot, ".factory", "commands", "plan.md"),
|
|
39
|
+
"utf8",
|
|
40
|
+
)
|
|
41
|
+
expect(commandContent).toContain("Plan command content")
|
|
42
|
+
|
|
43
|
+
const droidContent = await fs.readFile(
|
|
44
|
+
path.join(tempRoot, ".factory", "droids", "security-reviewer.md"),
|
|
45
|
+
"utf8",
|
|
46
|
+
)
|
|
47
|
+
expect(droidContent).toContain("Droid content")
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test("writes directly into a .factory output root", async () => {
|
|
51
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "droid-home-"))
|
|
52
|
+
const factoryRoot = path.join(tempRoot, ".factory")
|
|
53
|
+
const bundle: DroidBundle = {
|
|
54
|
+
commands: [{ name: "plan", content: "Plan content" }],
|
|
55
|
+
droids: [{ name: "reviewer", content: "Reviewer content" }],
|
|
56
|
+
skillDirs: [],
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await writeDroidBundle(factoryRoot, bundle)
|
|
60
|
+
|
|
61
|
+
expect(await exists(path.join(factoryRoot, "commands", "plan.md"))).toBe(true)
|
|
62
|
+
expect(await exists(path.join(factoryRoot, "droids", "reviewer.md"))).toBe(true)
|
|
63
|
+
// Should not double-nest under .factory/.factory
|
|
64
|
+
expect(await exists(path.join(factoryRoot, ".factory"))).toBe(false)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test("handles empty bundles gracefully", async () => {
|
|
68
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "droid-empty-"))
|
|
69
|
+
const bundle: DroidBundle = {
|
|
70
|
+
commands: [],
|
|
71
|
+
droids: [],
|
|
72
|
+
skillDirs: [],
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
await writeDroidBundle(tempRoot, bundle)
|
|
76
|
+
|
|
77
|
+
// Root should exist but no subdirectories created
|
|
78
|
+
expect(await exists(tempRoot)).toBe(true)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test("writes multiple commands as separate files", async () => {
|
|
82
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "droid-multi-"))
|
|
83
|
+
const factoryRoot = path.join(tempRoot, ".factory")
|
|
84
|
+
const bundle: DroidBundle = {
|
|
85
|
+
commands: [
|
|
86
|
+
{ name: "plan", content: "Plan content" },
|
|
87
|
+
{ name: "work", content: "Work content" },
|
|
88
|
+
{ name: "brainstorm", content: "Brainstorm content" },
|
|
89
|
+
],
|
|
90
|
+
droids: [],
|
|
91
|
+
skillDirs: [],
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await writeDroidBundle(factoryRoot, bundle)
|
|
95
|
+
|
|
96
|
+
expect(await exists(path.join(factoryRoot, "commands", "plan.md"))).toBe(true)
|
|
97
|
+
expect(await exists(path.join(factoryRoot, "commands", "work.md"))).toBe(true)
|
|
98
|
+
expect(await exists(path.join(factoryRoot, "commands", "brainstorm.md"))).toBe(true)
|
|
99
|
+
})
|
|
100
|
+
})
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: technical_review
|
|
3
|
-
description: Have multiple specialized agents review the technical approach and architecture of a plan in parallel
|
|
4
|
-
argument-hint: "[plan file path or plan content]"
|
|
5
|
-
disable-model-invocation: true
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Have @agent-dhh-rails-reviewer @agent-kieran-rails-reviewer @agent-code-simplicity-reviewer review the technical approach in this plan in parallel.
|