feat: fix skill transformation pipeline across all targets (#334)

This commit is contained in:
Trevin Chow
2026-03-21 19:45:20 -07:00
committed by GitHub
parent 0f6448d81c
commit 4087e1df82
33 changed files with 624 additions and 86 deletions

View File

@@ -248,6 +248,35 @@ Task compound-engineering:review:security-reviewer(code_diff)`,
expect(parsed.body).not.toContain("Task compound-engineering:")
})
test("transforms zero-argument Task calls", () => {
const plugin: ClaudePlugin = {
...fixturePlugin,
commands: [
{
name: "review",
description: "Review code",
body: `- Task compound-engineering:review:code-simplicity-reviewer()`,
sourcePath: "/tmp/plugin/commands/review.md",
},
],
agents: [],
skills: [],
}
const bundle = convertClaudeToCodex(plugin, {
agentMode: "subagent",
inferTemperature: false,
permissions: "none",
})
const commandSkill = bundle.generatedSkills.find((s) => s.name === "review")
expect(commandSkill).toBeDefined()
const parsed = parseFrontmatter(commandSkill!.content)
expect(parsed.body).toContain("Use the $code-simplicity-reviewer skill")
expect(parsed.body).not.toContain("compound-engineering:")
expect(parsed.body).not.toContain("skill to:")
})
test("transforms slash commands to prompts syntax", () => {
const plugin: ClaudePlugin = {
...fixturePlugin,

View File

@@ -177,6 +177,7 @@ Run these research agents:
Also run bare agents:
- Task best-practices-researcher(topic)
- Task compound-engineering:review:code-simplicity-reviewer()
`,
)
@@ -205,6 +206,10 @@ Also run bare agents:
// Bare Task calls should still be rewritten
expect(installedSkill).toContain("Use the $best-practices-researcher skill to: topic")
expect(installedSkill).not.toContain("Task best-practices-researcher")
// Zero-arg Task calls should be rewritten without trailing "to:"
expect(installedSkill).toContain("Use the $code-simplicity-reviewer skill")
expect(installedSkill).not.toContain("code-simplicity-reviewer skill to:")
})
test("preserves unknown slash text in copied SKILL.md files", async () => {

View File

@@ -444,6 +444,27 @@ Task best-practices-researcher(topic)`
expect(result).not.toContain("Task repo-research-analyst(")
})
test("transforms namespaced Task agent calls using final segment", () => {
const input = `Run agents:
- Task compound-engineering:research:repo-research-analyst(feature_description)
- Task compound-engineering:review:security-reviewer(code_diff)`
const result = transformContentForCopilot(input)
expect(result).toContain("Use the repo-research-analyst skill to: feature_description")
expect(result).toContain("Use the security-reviewer skill to: code_diff")
expect(result).not.toContain("compound-engineering:")
})
test("transforms zero-argument Task calls", () => {
const input = `- Task compound-engineering:review:code-simplicity-reviewer()`
const result = transformContentForCopilot(input)
expect(result).toContain("Use the code-simplicity-reviewer skill")
expect(result).not.toContain("compound-engineering:")
expect(result).not.toContain("skill to:")
})
test("replaces colons with hyphens in slash commands", () => {
const input = `1. Run /deepen-plan to enhance
2. Start /workflows:work to implement

View File

@@ -165,6 +165,44 @@ describe("writeCopilotBundle", () => {
expect(backupFiles.length).toBeGreaterThanOrEqual(1)
})
test("transforms Task calls in copied SKILL.md files", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "copilot-skill-transform-"))
const sourceSkillDir = path.join(tempRoot, "source-skill")
await fs.mkdir(sourceSkillDir, { recursive: true })
await fs.writeFile(
path.join(sourceSkillDir, "SKILL.md"),
`---
name: ce:plan
description: Planning workflow
---
Run these research agents:
- Task compound-engineering:research:repo-research-analyst(feature_description)
- Task compound-engineering:research:learnings-researcher(feature_description)
- Task compound-engineering:review:code-simplicity-reviewer()
`,
)
const bundle: CopilotBundle = {
agents: [],
generatedSkills: [],
skillDirs: [{ name: "ce:plan", sourceDir: sourceSkillDir }],
}
await writeCopilotBundle(tempRoot, bundle)
const installedSkill = await fs.readFile(
path.join(tempRoot, ".github", "skills", "ce:plan", "SKILL.md"),
"utf8",
)
expect(installedSkill).toContain("Use the repo-research-analyst skill to: feature_description")
expect(installedSkill).toContain("Use the learnings-researcher skill to: feature_description")
expect(installedSkill).toContain("Use the code-simplicity-reviewer skill")
expect(installedSkill).not.toContain("Task compound-engineering:")
})
test("creates skill directories with SKILL.md", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "copilot-genskill-"))
const bundle: CopilotBundle = {

View File

@@ -148,6 +148,63 @@ Task best-practices-researcher(topic)`,
expect(parsed.body).not.toContain("Task repo-research-analyst(")
})
test("transforms namespaced Task agent calls using final segment", () => {
const plugin: ClaudePlugin = {
...fixturePlugin,
commands: [
{
name: "plan",
description: "Planning with namespaced agents",
body: `Run agents:
- Task compound-engineering:research:repo-research-analyst(feature_description)
- Task compound-engineering:review:security-reviewer(code_diff)`,
sourcePath: "/tmp/plugin/commands/plan.md",
},
],
agents: [],
skills: [],
}
const bundle = convertClaudeToDroid(plugin, {
agentMode: "subagent",
inferTemperature: false,
permissions: "none",
})
const parsed = parseFrontmatter(bundle.commands[0].content)
expect(parsed.body).toContain("Task repo-research-analyst: feature_description")
expect(parsed.body).toContain("Task security-reviewer: code_diff")
expect(parsed.body).not.toContain("compound-engineering:")
})
test("transforms zero-argument Task calls", () => {
const plugin: ClaudePlugin = {
...fixturePlugin,
commands: [
{
name: "review",
description: "Review code",
body: `- Task compound-engineering:review:code-simplicity-reviewer()`,
sourcePath: "/tmp/plugin/commands/review.md",
},
],
agents: [],
skills: [],
}
const bundle = convertClaudeToDroid(plugin, {
agentMode: "subagent",
inferTemperature: false,
permissions: "none",
})
const parsed = parseFrontmatter(bundle.commands[0].content)
expect(parsed.body).toContain("Task code-simplicity-reviewer")
expect(parsed.body).not.toContain("compound-engineering:")
expect(parsed.body).not.toContain("()")
})
test("transforms slash commands by flattening namespaces", () => {
const plugin: ClaudePlugin = {
...fixturePlugin,

View File

@@ -47,6 +47,44 @@ describe("writeDroidBundle", () => {
expect(droidContent).toContain("Droid content")
})
test("transforms Task calls in copied SKILL.md files", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "droid-skill-transform-"))
const sourceSkillDir = path.join(tempRoot, "source-skill")
await fs.mkdir(sourceSkillDir, { recursive: true })
await fs.writeFile(
path.join(sourceSkillDir, "SKILL.md"),
`---
name: ce:plan
description: Planning workflow
---
Run these research agents:
- Task compound-engineering:research:repo-research-analyst(feature_description)
- Task compound-engineering:research:learnings-researcher(feature_description)
- Task compound-engineering:review:code-simplicity-reviewer()
`,
)
const bundle: DroidBundle = {
commands: [],
droids: [],
skillDirs: [{ name: "ce:plan", sourceDir: sourceSkillDir }],
}
await writeDroidBundle(tempRoot, bundle)
const installedSkill = await fs.readFile(
path.join(tempRoot, ".factory", "skills", "ce:plan", "SKILL.md"),
"utf8",
)
expect(installedSkill).toContain("Task repo-research-analyst: feature_description")
expect(installedSkill).toContain("Task learnings-researcher: feature_description")
expect(installedSkill).toContain("Task code-simplicity-reviewer")
expect(installedSkill).not.toContain("Task compound-engineering:")
})
test("writes directly into a .factory output root", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "droid-home-"))
const factoryRoot = path.join(tempRoot, ".factory")

View File

@@ -338,6 +338,27 @@ Task best-practices-researcher(topic)`
expect(result).not.toContain("Task repo-research-analyst")
})
test("transforms namespaced Task agent calls using final segment", () => {
const input = `Run agents:
- Task compound-engineering:research:repo-research-analyst(feature_description)
- Task compound-engineering:review:security-reviewer(code_diff)`
const result = transformContentForGemini(input)
expect(result).toContain("Use the repo-research-analyst skill to: feature_description")
expect(result).toContain("Use the security-reviewer skill to: code_diff")
expect(result).not.toContain("compound-engineering:")
})
test("transforms zero-argument Task calls", () => {
const input = `- Task compound-engineering:review:code-simplicity-reviewer()`
const result = transformContentForGemini(input)
expect(result).toContain("Use the code-simplicity-reviewer skill")
expect(result).not.toContain("compound-engineering:")
expect(result).not.toContain("skill to:")
})
test("transforms @agent references to skill references", () => {
const result = transformContentForGemini("Ask @security-sentinel for a review.")
expect(result).toContain("the security-sentinel skill")

View File

@@ -66,6 +66,44 @@ describe("writeGeminiBundle", () => {
expect(settingsContent.mcpServers.playwright.command).toBe("npx")
})
test("transforms Task calls in copied SKILL.md files", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "gemini-skill-transform-"))
const sourceSkillDir = path.join(tempRoot, "source-skill")
await fs.mkdir(sourceSkillDir, { recursive: true })
await fs.writeFile(
path.join(sourceSkillDir, "SKILL.md"),
`---
name: ce:plan
description: Planning workflow
---
Run these research agents:
- Task compound-engineering:research:repo-research-analyst(feature_description)
- Task compound-engineering:research:learnings-researcher(feature_description)
- Task compound-engineering:review:code-simplicity-reviewer()
`,
)
const bundle: GeminiBundle = {
generatedSkills: [],
skillDirs: [{ name: "ce:plan", sourceDir: sourceSkillDir }],
commands: [],
}
await writeGeminiBundle(tempRoot, bundle)
const installedSkill = await fs.readFile(
path.join(tempRoot, ".gemini", "skills", "ce:plan", "SKILL.md"),
"utf8",
)
expect(installedSkill).toContain("Use the repo-research-analyst skill to: feature_description")
expect(installedSkill).toContain("Use the learnings-researcher skill to: feature_description")
expect(installedSkill).toContain("Use the code-simplicity-reviewer skill")
expect(installedSkill).not.toContain("Task compound-engineering:")
})
test("namespaced commands create subdirectories", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "gemini-ns-"))
const bundle: GeminiBundle = {

View File

@@ -391,6 +391,27 @@ Task best-practices-researcher(topic)`
expect(result).not.toContain("Task repo-research-analyst")
})
test("transforms namespaced Task agent calls using final segment", () => {
const input = `Run agents:
- Task compound-engineering:research:repo-research-analyst(feature_description)
- Task compound-engineering:review:security-reviewer(code_diff)`
const result = transformContentForKiro(input)
expect(result).toContain("Use the use_subagent tool to delegate to the repo-research-analyst agent: feature_description")
expect(result).toContain("Use the use_subagent tool to delegate to the security-reviewer agent: code_diff")
expect(result).not.toContain("compound-engineering:")
})
test("transforms zero-argument Task calls", () => {
const input = `- Task compound-engineering:review:code-simplicity-reviewer()`
const result = transformContentForKiro(input)
expect(result).toContain("Use the use_subagent tool to delegate to the code-simplicity-reviewer agent")
expect(result).not.toContain("compound-engineering:")
expect(result).not.toContain("code-simplicity-reviewer agent:")
})
test("transforms @agent references for known agents only", () => {
const result = transformContentForKiro("Ask @security-sentinel for a review.", ["security-sentinel"])
expect(result).toContain("the security-sentinel agent")

View File

@@ -99,6 +99,43 @@ describe("writeKiroBundle", () => {
expect(mcpContent.mcpServers.playwright.command).toBe("npx")
})
test("transforms Task calls in copied SKILL.md files", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "kiro-skill-transform-"))
const sourceSkillDir = path.join(tempRoot, "source-skill")
await fs.mkdir(sourceSkillDir, { recursive: true })
await fs.writeFile(
path.join(sourceSkillDir, "SKILL.md"),
`---
name: ce:plan
description: Planning workflow
---
Run these research agents:
- Task compound-engineering:research:repo-research-analyst(feature_description)
- Task compound-engineering:research:learnings-researcher(feature_description)
- Task compound-engineering:review:code-simplicity-reviewer()
`,
)
const bundle: KiroBundle = {
...emptyBundle,
skillDirs: [{ name: "ce:plan", sourceDir: sourceSkillDir }],
}
await writeKiroBundle(tempRoot, bundle)
const installedSkill = await fs.readFile(
path.join(tempRoot, ".kiro", "skills", "ce:plan", "SKILL.md"),
"utf8",
)
expect(installedSkill).toContain("Use the use_subagent tool to delegate to the repo-research-analyst agent: feature_description")
expect(installedSkill).toContain("Use the use_subagent tool to delegate to the learnings-researcher agent: feature_description")
expect(installedSkill).toContain("Use the use_subagent tool to delegate to the code-simplicity-reviewer agent")
expect(installedSkill).not.toContain("Task compound-engineering:")
})
test("does not double-nest when output root is .kiro", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "kiro-home-"))
const kiroRoot = path.join(tempRoot, ".kiro")

View File

@@ -85,6 +85,70 @@ describe("convertClaudeToPi", () => {
expect(parsedPrompt.body).toContain("file-based todos (todos/ + /skill:file-todos)")
})
test("transforms namespaced Task agent calls using final segment", () => {
const plugin: ClaudePlugin = {
root: "/tmp/plugin",
manifest: { name: "fixture", version: "1.0.0" },
agents: [],
commands: [
{
name: "plan",
description: "Planning with namespaced agents",
body: [
"Run agents:",
"- Task compound-engineering:research:repo-research-analyst(feature_description)",
"- Task compound-engineering:review:security-reviewer(code_diff)",
].join("\n"),
sourcePath: "/tmp/plugin/commands/plan.md",
},
],
skills: [],
hooks: undefined,
mcpServers: undefined,
}
const bundle = convertClaudeToPi(plugin, {
agentMode: "subagent",
inferTemperature: false,
permissions: "none",
})
const parsedPrompt = parseFrontmatter(bundle.prompts[0].content)
expect(parsedPrompt.body).toContain('Run subagent with agent="repo-research-analyst" and task="feature_description".')
expect(parsedPrompt.body).toContain('Run subagent with agent="security-reviewer" and task="code_diff".')
expect(parsedPrompt.body).not.toContain("compound-engineering:")
})
test("transforms zero-argument Task calls", () => {
const plugin: ClaudePlugin = {
root: "/tmp/plugin",
manifest: { name: "fixture", version: "1.0.0" },
agents: [],
commands: [
{
name: "review",
description: "Review code",
body: "- Task compound-engineering:review:code-simplicity-reviewer()",
sourcePath: "/tmp/plugin/commands/review.md",
},
],
skills: [],
hooks: undefined,
mcpServers: undefined,
}
const bundle = convertClaudeToPi(plugin, {
agentMode: "subagent",
inferTemperature: false,
permissions: "none",
})
const parsedPrompt = parseFrontmatter(bundle.prompts[0].content)
expect(parsedPrompt.body).toContain('Run subagent with agent="code-simplicity-reviewer".')
expect(parsedPrompt.body).not.toContain("compound-engineering:")
expect(parsedPrompt.body).not.toContain("()")
})
test("appends MCPorter compatibility note when command references MCP", () => {
const plugin: ClaudePlugin = {
root: "/tmp/plugin",

View File

@@ -50,6 +50,46 @@ describe("writePiBundle", () => {
expect(agentsContent).toContain("MCPorter")
})
test("transforms Task calls in copied SKILL.md files", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "pi-skill-transform-"))
const outputRoot = path.join(tempRoot, ".pi")
const sourceSkillDir = path.join(tempRoot, "source-skill")
await fs.mkdir(sourceSkillDir, { recursive: true })
await fs.writeFile(
path.join(sourceSkillDir, "SKILL.md"),
`---
name: ce:plan
description: Planning workflow
---
Run these research agents:
- Task compound-engineering:research:repo-research-analyst(feature_description)
- Task compound-engineering:research:learnings-researcher(feature_description)
- Task compound-engineering:review:code-simplicity-reviewer()
`,
)
const bundle: PiBundle = {
prompts: [],
skillDirs: [{ name: "ce:plan", sourceDir: sourceSkillDir }],
generatedSkills: [],
extensions: [],
}
await writePiBundle(outputRoot, bundle)
const installedSkill = await fs.readFile(
path.join(outputRoot, "skills", "ce:plan", "SKILL.md"),
"utf8",
)
expect(installedSkill).toContain('Run subagent with agent="repo-research-analyst" and task="feature_description".')
expect(installedSkill).toContain('Run subagent with agent="learnings-researcher" and task="feature_description".')
expect(installedSkill).toContain('Run subagent with agent="code-simplicity-reviewer".')
expect(installedSkill).not.toContain("Task compound-engineering:")
})
test("writes to ~/.pi/agent style roots without nesting under .pi", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "pi-agent-root-"))
const outputRoot = path.join(tempRoot, "agent")

View File

@@ -508,6 +508,27 @@ Task best-practices-researcher(topic)`
expect(result).not.toContain("Task repo-research-analyst")
})
test("transforms namespaced Task agent calls using final segment", () => {
const input = `Run agents:
- Task compound-engineering:research:repo-research-analyst(feature_description)
- Task compound-engineering:review:security-reviewer(code_diff)`
const result = transformContentForWindsurf(input)
expect(result).toContain("Use the @repo-research-analyst skill: feature_description")
expect(result).toContain("Use the @security-reviewer skill: code_diff")
expect(result).not.toContain("compound-engineering:")
})
test("transforms zero-argument Task calls", () => {
const input = `- Task compound-engineering:review:code-simplicity-reviewer()`
const result = transformContentForWindsurf(input)
expect(result).toContain("Use the @code-simplicity-reviewer skill")
expect(result).not.toContain("compound-engineering:")
expect(result).not.toContain("code-simplicity-reviewer skill:")
})
test("keeps @agent references as-is for known agents (Windsurf skill invocation syntax)", () => {
const result = transformContentForWindsurf("Ask @security-sentinel for a review.", ["security-sentinel"])
expect(result).toContain("@security-sentinel")

View File

@@ -85,6 +85,43 @@ describe("writeWindsurfBundle", () => {
expect(mcpContent.mcpServers.local).toEqual({ command: "echo", args: ["hello"] })
})
test("transforms Task calls in copied SKILL.md files", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "windsurf-skill-transform-"))
const sourceSkillDir = path.join(tempRoot, "source-skill")
await fs.mkdir(sourceSkillDir, { recursive: true })
await fs.writeFile(
path.join(sourceSkillDir, "SKILL.md"),
`---
name: ce:plan
description: Planning workflow
---
Run these research agents:
- Task compound-engineering:research:repo-research-analyst(feature_description)
- Task compound-engineering:research:learnings-researcher(feature_description)
- Task compound-engineering:review:code-simplicity-reviewer()
`,
)
const bundle: WindsurfBundle = {
...emptyBundle,
skillDirs: [{ name: "ce:plan", sourceDir: sourceSkillDir }],
}
await writeWindsurfBundle(tempRoot, bundle)
const installedSkill = await fs.readFile(
path.join(tempRoot, "skills", "ce:plan", "SKILL.md"),
"utf8",
)
expect(installedSkill).toContain("Use the @repo-research-analyst skill: feature_description")
expect(installedSkill).toContain("Use the @learnings-researcher skill: feature_description")
expect(installedSkill).toContain("Use the @code-simplicity-reviewer skill")
expect(installedSkill).not.toContain("Task compound-engineering:")
})
test("writes directly into outputRoot without nesting", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "windsurf-direct-"))
const bundle: WindsurfBundle = {