Files
claude-engineering-plugin/tests/codex-writer.test.ts
Trevin Chow c65a698d93 fix(converters): preserve user config when writing MCP servers (#479)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 11:46:57 -07:00

529 lines
18 KiB
TypeScript

import { describe, expect, test } from "bun:test"
import { promises as fs } from "fs"
import path from "path"
import os from "os"
import { mergeCodexConfig, renderCodexConfig, writeCodexBundle } from "../src/targets/codex"
import type { CodexBundle } from "../src/types/codex"
async function exists(filePath: string): Promise<boolean> {
try {
await fs.access(filePath)
return true
} catch {
return false
}
}
describe("writeCodexBundle", () => {
test("writes prompts, skills, and config", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "codex-test-"))
const bundle: CodexBundle = {
prompts: [{ name: "command-one", content: "Prompt content" }],
skillDirs: [
{
name: "skill-one",
sourceDir: path.join(import.meta.dir, "fixtures", "sample-plugin", "skills", "skill-one"),
},
],
generatedSkills: [{ name: "agent-skill", content: "Skill content" }],
mcpServers: {
local: { command: "echo", args: ["hello"], env: { KEY: "VALUE" } },
remote: {
url: "https://example.com/mcp",
headers: { Authorization: "Bearer token" },
},
},
}
await writeCodexBundle(tempRoot, bundle)
expect(await exists(path.join(tempRoot, ".codex", "prompts", "command-one.md"))).toBe(true)
expect(await exists(path.join(tempRoot, ".codex", "skills", "skill-one", "SKILL.md"))).toBe(true)
expect(await exists(path.join(tempRoot, ".codex", "skills", "agent-skill", "SKILL.md"))).toBe(true)
const configPath = path.join(tempRoot, ".codex", "config.toml")
expect(await exists(configPath)).toBe(true)
const config = await fs.readFile(configPath, "utf8")
expect(config).toContain("# BEGIN Compound Engineering plugin MCP -- do not edit this block")
expect(config).toContain("# END Compound Engineering plugin MCP")
expect(config).toContain("[mcp_servers.local]")
expect(config).toContain("command = \"echo\"")
expect(config).toContain("args = [\"hello\"]")
expect(config).toContain("[mcp_servers.local.env]")
expect(config).toContain("KEY = \"VALUE\"")
expect(config).toContain("[mcp_servers.remote]")
expect(config).toContain("url = \"https://example.com/mcp\"")
expect(config).toContain("http_headers")
})
test("writes directly into a .codex output root", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "codex-home-"))
const codexRoot = path.join(tempRoot, ".codex")
const bundle: CodexBundle = {
prompts: [{ name: "command-one", content: "Prompt content" }],
skillDirs: [
{
name: "skill-one",
sourceDir: path.join(import.meta.dir, "fixtures", "sample-plugin", "skills", "skill-one"),
},
],
generatedSkills: [],
}
await writeCodexBundle(codexRoot, bundle)
expect(await exists(path.join(codexRoot, "prompts", "command-one.md"))).toBe(true)
expect(await exists(path.join(codexRoot, "skills", "skill-one", "SKILL.md"))).toBe(true)
})
test("preserves existing user config when writing MCP servers", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "codex-backup-"))
const codexRoot = path.join(tempRoot, ".codex")
const configPath = path.join(codexRoot, "config.toml")
// Create existing config with user settings
await fs.mkdir(codexRoot, { recursive: true })
const originalContent = "# My original config\n[custom]\nkey = \"value\"\n"
await fs.writeFile(configPath, originalContent)
const bundle: CodexBundle = {
prompts: [],
skillDirs: [],
generatedSkills: [],
mcpServers: { test: { command: "echo" } },
}
await writeCodexBundle(codexRoot, bundle)
const newConfig = await fs.readFile(configPath, "utf8")
// Plugin MCP servers should be present in a managed block
expect(newConfig).toContain("[mcp_servers.test]")
expect(newConfig).toContain("# BEGIN Compound Engineering plugin MCP -- do not edit this block")
expect(newConfig).toContain("# END Compound Engineering plugin MCP")
// User's original config should be preserved
expect(newConfig).toContain("# My original config")
expect(newConfig).toContain("[custom]")
expect(newConfig).toContain('key = "value"')
// Backup should still exist with original content
const files = await fs.readdir(codexRoot)
const backupFileName = files.find((f) => f.startsWith("config.toml.bak."))
expect(backupFileName).toBeDefined()
const backupContent = await fs.readFile(path.join(codexRoot, backupFileName!), "utf8")
expect(backupContent).toBe(originalContent)
})
test("is idempotent — running twice does not duplicate managed block", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "codex-idempotent-"))
const codexRoot = path.join(tempRoot, ".codex")
const configPath = path.join(codexRoot, "config.toml")
await fs.mkdir(codexRoot, { recursive: true })
await fs.writeFile(configPath, "[user]\nmodel = \"gpt-4.1\"\n")
const bundle: CodexBundle = {
prompts: [],
skillDirs: [],
generatedSkills: [],
mcpServers: { test: { command: "echo" } },
}
await writeCodexBundle(codexRoot, bundle)
await writeCodexBundle(codexRoot, bundle)
const config = await fs.readFile(configPath, "utf8")
expect(config.match(/# BEGIN Compound Engineering plugin MCP/g)?.length).toBe(1)
expect(config.match(/# END Compound Engineering plugin MCP/g)?.length).toBe(1)
expect(config).toContain("[user]")
})
test("migrates old managed block markers to new ones", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "codex-migrate-"))
const codexRoot = path.join(tempRoot, ".codex")
const configPath = path.join(codexRoot, "config.toml")
await fs.mkdir(codexRoot, { recursive: true })
await fs.writeFile(configPath, [
"[user]",
'model = "gpt-4.1"',
"",
"# BEGIN compound-plugin Claude Code MCP",
"[mcp_servers.old]",
'command = "old"',
"# END compound-plugin Claude Code MCP",
].join("\n"))
const bundle: CodexBundle = {
prompts: [],
skillDirs: [],
generatedSkills: [],
mcpServers: { fresh: { command: "new" } },
}
await writeCodexBundle(codexRoot, bundle)
const config = await fs.readFile(configPath, "utf8")
expect(config).not.toContain("# BEGIN compound-plugin Claude Code MCP")
expect(config).toContain("# BEGIN Compound Engineering plugin MCP")
expect(config).not.toContain("[mcp_servers.old]")
expect(config).toContain("[mcp_servers.fresh]")
expect(config).toContain("[user]")
})
test("migrates unmarked legacy format (# Generated by compound-plugin)", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "codex-unmarked-"))
const codexRoot = path.join(tempRoot, ".codex")
const configPath = path.join(codexRoot, "config.toml")
// Simulate old writer output: entire file was just the generated config
await fs.mkdir(codexRoot, { recursive: true })
await fs.writeFile(configPath, [
"# Generated by compound-plugin",
"",
"[mcp_servers.old]",
'command = "old"',
"",
].join("\n"))
const bundle: CodexBundle = {
prompts: [],
skillDirs: [],
generatedSkills: [],
mcpServers: { fresh: { command: "new" } },
}
await writeCodexBundle(codexRoot, bundle)
const config = await fs.readFile(configPath, "utf8")
expect(config).not.toContain("# Generated by compound-plugin")
expect(config).not.toContain("[mcp_servers.old]")
expect(config).toContain("# BEGIN Compound Engineering plugin MCP")
expect(config).toContain("[mcp_servers.fresh]")
// Should have exactly one BEGIN marker (no duplication)
expect(config.match(/# BEGIN Compound Engineering plugin MCP/g)?.length).toBe(1)
})
test("strips stale managed block when plugin has no MCP servers", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "codex-stale-"))
const codexRoot = path.join(tempRoot, ".codex")
const configPath = path.join(codexRoot, "config.toml")
await fs.mkdir(codexRoot, { recursive: true })
await fs.writeFile(configPath, [
"[user]",
'model = "gpt-4.1"',
"",
"# BEGIN Compound Engineering plugin MCP -- do not edit this block",
"[mcp_servers.stale]",
'command = "should-be-removed"',
"# END Compound Engineering plugin MCP",
].join("\n"))
await writeCodexBundle(codexRoot, { prompts: [], skillDirs: [], generatedSkills: [] })
const config = await fs.readFile(configPath, "utf8")
expect(config).not.toContain("mcp_servers.stale")
expect(config).not.toContain("# BEGIN Compound Engineering")
expect(config).toContain("[user]")
})
test("transforms copied SKILL.md files using Codex invocation targets", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "codex-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:brainstorm
description: Brainstorm workflow
---
Continue with /ce:plan when ready.
Or use /workflows:plan if you're following an older doc.
Use /todo-resolve for deeper research.
`,
)
await fs.writeFile(
path.join(sourceSkillDir, "notes.md"),
"Reference docs still mention /ce:plan here.\n",
)
const bundle: CodexBundle = {
prompts: [],
skillDirs: [{ name: "ce:brainstorm", sourceDir: sourceSkillDir }],
generatedSkills: [],
invocationTargets: {
promptTargets: {
"ce-plan": "ce-plan",
"workflows-plan": "ce-plan",
"todo-resolve": "todo-resolve",
},
skillTargets: {},
},
}
await writeCodexBundle(tempRoot, bundle)
const installedSkill = await fs.readFile(
path.join(tempRoot, ".codex", "skills", "ce-brainstorm", "SKILL.md"),
"utf8",
)
expect(installedSkill).toContain("/prompts:ce-plan")
expect(installedSkill).not.toContain("/workflows:plan")
expect(installedSkill).toContain("/prompts:todo-resolve")
const notes = await fs.readFile(
path.join(tempRoot, ".codex", "skills", "ce-brainstorm", "notes.md"),
"utf8",
)
expect(notes).toContain("/ce:plan")
})
test("transforms namespaced Task calls in copied SKILL.md files", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "codex-ns-task-"))
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)
Also run bare agents:
- Task best-practices-researcher(topic)
- Task compound-engineering:review:code-simplicity-reviewer()
`,
)
const bundle: CodexBundle = {
prompts: [],
skillDirs: [{ name: "ce:plan", sourceDir: sourceSkillDir }],
generatedSkills: [],
invocationTargets: {
promptTargets: {},
skillTargets: {},
},
}
await writeCodexBundle(tempRoot, bundle)
const installedSkill = await fs.readFile(
path.join(tempRoot, ".codex", "skills", "ce-plan", "SKILL.md"),
"utf8",
)
// Namespaced Task calls should be rewritten using the final segment
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).not.toContain("Task compound-engineering:")
// 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 () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "codex-skill-preserve-"))
const sourceSkillDir = path.join(tempRoot, "source-skill")
await fs.mkdir(sourceSkillDir, { recursive: true })
await fs.writeFile(
path.join(sourceSkillDir, "SKILL.md"),
`---
name: proof
description: Proof skill
---
Route examples:
- /users
- /settings
API examples:
- https://www.proofeditor.ai/api/agent/{slug}/state
- https://www.proofeditor.ai/share/markdown
Workflow handoff:
- /ce:plan
`,
)
const bundle: CodexBundle = {
prompts: [],
skillDirs: [{ name: "proof", sourceDir: sourceSkillDir }],
generatedSkills: [],
invocationTargets: {
promptTargets: {
"ce-plan": "ce-plan",
},
skillTargets: {},
},
}
await writeCodexBundle(tempRoot, bundle)
const installedSkill = await fs.readFile(
path.join(tempRoot, ".codex", "skills", "proof", "SKILL.md"),
"utf8",
)
expect(installedSkill).toContain("/users")
expect(installedSkill).toContain("/settings")
expect(installedSkill).toContain("https://www.proofeditor.ai/api/agent/{slug}/state")
expect(installedSkill).toContain("https://www.proofeditor.ai/share/markdown")
expect(installedSkill).toContain("/prompts:ce-plan")
expect(installedSkill).not.toContain("/prompts:users")
expect(installedSkill).not.toContain("/prompts:settings")
expect(installedSkill).not.toContain("https://prompts:www.proofeditor.ai")
})
})
describe("renderCodexConfig", () => {
test("skips servers with neither command nor url", () => {
const result = renderCodexConfig({ broken: {} })
expect(result).toBeNull()
})
test("skips malformed servers but keeps valid ones", () => {
const result = renderCodexConfig({
valid: { command: "echo" },
broken: {},
alsoValid: { url: "https://example.com/mcp" },
})
expect(result).not.toBeNull()
expect(result).toContain("[mcp_servers.valid]")
expect(result).toContain("[mcp_servers.alsoValid]")
expect(result).not.toContain("[mcp_servers.broken]")
})
test("returns null for empty or undefined input", () => {
expect(renderCodexConfig(undefined)).toBeNull()
expect(renderCodexConfig({})).toBeNull()
})
})
describe("mergeCodexConfig", () => {
test("returns managed block when no existing content", () => {
const result = mergeCodexConfig("", "[mcp_servers.test]\ncommand = \"echo\"")
expect(result).toContain("# BEGIN Compound Engineering plugin MCP")
expect(result).toContain("[mcp_servers.test]")
expect(result).toContain("# END Compound Engineering plugin MCP")
})
test("preserves user content and replaces managed block", () => {
const existing = [
"[user]",
'model = "gpt-4.1"',
"",
"# BEGIN Compound Engineering plugin MCP -- do not edit this block",
"[mcp_servers.old]",
'command = "old"',
"# END Compound Engineering plugin MCP",
"",
"[after]",
'key = "value"',
].join("\n")
const result = mergeCodexConfig(existing, "[mcp_servers.new]\ncommand = \"new\"")!
expect(result).toContain("[user]")
expect(result).toContain("[after]")
expect(result).not.toContain("[mcp_servers.old]")
expect(result).toContain("[mcp_servers.new]")
})
test("strips previous-generation markers", () => {
const existing = [
"[user]",
'model = "gpt-4.1"',
"",
"# BEGIN compound-plugin Claude Code MCP",
"[mcp_servers.old]",
'command = "old"',
"# END compound-plugin Claude Code MCP",
].join("\n")
const result = mergeCodexConfig(existing, "[mcp_servers.new]\ncommand = \"new\"")!
expect(result).not.toContain("# BEGIN compound-plugin Claude Code MCP")
expect(result).not.toContain("[mcp_servers.old]")
expect(result).toContain("# BEGIN Compound Engineering plugin MCP")
expect(result).toContain("[mcp_servers.new]")
})
test("returns cleaned content (no block) when mcpToml is null", () => {
const existing = [
"[user]",
'model = "gpt-4.1"',
"",
"# BEGIN Compound Engineering plugin MCP -- do not edit this block",
"[mcp_servers.stale]",
'command = "stale"',
"# END Compound Engineering plugin MCP",
].join("\n")
const result = mergeCodexConfig(existing, null)!
expect(result).toContain("[user]")
expect(result).not.toContain("mcp_servers.stale")
expect(result).not.toContain("# BEGIN")
})
test("strips unmarked legacy format (# Generated by compound-plugin)", () => {
const existing = [
"# Generated by compound-plugin",
"",
"[mcp_servers.old]",
'command = "old"',
"",
].join("\n")
const result = mergeCodexConfig(existing, "[mcp_servers.new]\ncommand = \"new\"")!
expect(result).not.toContain("# Generated by compound-plugin")
expect(result).not.toContain("[mcp_servers.old]")
expect(result).toContain("# BEGIN Compound Engineering plugin MCP")
expect(result).toContain("[mcp_servers.new]")
})
test("preserves user config before unmarked legacy format", () => {
const existing = [
"[user]",
'model = "gpt-4.1"',
"",
"# Generated by compound-plugin",
"",
"[mcp_servers.old]",
'command = "old"',
].join("\n")
const result = mergeCodexConfig(existing, "[mcp_servers.new]\ncommand = \"new\"")!
expect(result).toContain("[user]")
expect(result).not.toContain("# Generated by compound-plugin")
expect(result).not.toContain("[mcp_servers.old]")
expect(result).toContain("[mcp_servers.new]")
})
test("returns null when no existing content and no mcpToml", () => {
expect(mergeCodexConfig("", null)).toBeNull()
})
test("returns empty string when file was only a managed block and mcpToml is null", () => {
const existing = [
"# BEGIN Compound Engineering plugin MCP -- do not edit this block",
"[mcp_servers.stale]",
'command = "stale"',
"# END Compound Engineering plugin MCP",
].join("\n")
const result = mergeCodexConfig(existing, null)
expect(result).toBe("")
})
})