Skip to content

Commit 4b78157

Browse files
committed
Add OpenAI Codex adapter (.codex/skills/), 4 tool adapters total
Adapters: github-copilot, claude, codex, opencode Agent values: github, claude, codex, opencode 85 tests passing
1 parent 534ff98 commit 4b78157

5 files changed

Lines changed: 57 additions & 5 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ zone.clanker.openspec.agents=github
5252
|-------------------|---------------------------------------------|
5353
| `github` | Generates GitHub Copilot files (`.github/`) |
5454
| `claude` | Generates Claude Code files (`.claude/`) |
55+
| `codex` | Generates OpenAI Codex files (`.codex/`) |
5556
| `opencode` | Generates OpenCode/Crush files (`.opencode/`) |
5657
| `github,claude` | Generates for multiple (comma-separated) |
5758
| `none` or empty | Cleans all generated files |

build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ gradlePlugin {
3838
id = "zone.clanker.gradle"
3939
implementationClass = "zone.clanker.gradle.OpenSpecSettingsPlugin"
4040
displayName = "OpenSpec Gradle Plugin"
41-
description = "Settings plugin that generates Markdown skill and command files for AI coding assistants (GitHub Copilot, Claude Code, OpenCode/Crush). Auto-applies to root project via init script."
42-
tags.set(listOf("ai", "copilot", "claude", "opencode", "openspec", "skills", "prompts", "coding-assistant"))
41+
description = "Settings plugin that generates Markdown skill and command files for AI coding assistants (GitHub Copilot, Claude Code, OpenAI Codex, OpenCode/Crush). Auto-applies to root project via init script."
42+
tags.set(listOf("ai", "copilot", "claude", "codex", "opencode", "openspec", "skills", "prompts", "coding-assistant"))
4343
}
4444
}
4545
}

src/main/kotlin/zone/clanker/gradle/OpenSpecSettingsPlugin.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class OpenSpecSettingsPlugin : Plugin<Settings> {
2525
private val AGENT_MAP = mapOf(
2626
"github" to "github-copilot",
2727
"claude" to "claude",
28+
"codex" to "codex",
2829
"opencode" to "opencode"
2930
)
3031

src/main/kotlin/zone/clanker/gradle/generators/ToolAdapter.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,24 @@ object GitHubCopilotAdapter : ToolAdapter {
101101
override fun formatSkillFile(content: SkillContent) = formatSkillWithFrontmatter(content)
102102
}
103103

104+
object CodexAdapter : ToolAdapter {
105+
override val toolId = "codex"
106+
// Codex uses .codex/skills/<name>/SKILL.md — skills are invoked via $skill-name
107+
// Codex doesn't have a separate commands directory — skills ARE the commands
108+
override fun getCommandFilePath(commandId: String) = ".codex/skills/opsx-$commandId/SKILL.md"
109+
override fun formatCommandFile(content: CommandContent) = buildString {
110+
// Format commands as skills since Codex unifies them
111+
val asSkill = SkillContent(
112+
dirName = "opsx-${content.id}",
113+
description = content.description,
114+
instructions = content.body
115+
)
116+
append(formatSkillWithFrontmatter(asSkill))
117+
}
118+
override fun getSkillFilePath(skillDirName: String) = ".codex/skills/$skillDirName/SKILL.md"
119+
override fun formatSkillFile(content: SkillContent) = formatSkillWithFrontmatter(content)
120+
}
121+
104122
object OpenCodeAdapter : ToolAdapter {
105123
override val toolId = "opencode"
106124
// OpenCode reads commands from .opencode/commands/*.md (project-level)
@@ -123,6 +141,7 @@ object ToolAdapterRegistry {
123141
private val adapters = mapOf(
124142
"claude" to ClaudeAdapter,
125143
"github-copilot" to GitHubCopilotAdapter,
144+
"codex" to CodexAdapter,
126145
"opencode" to OpenCodeAdapter
127146
)
128147

src/test/kotlin/zone/clanker/gradle/ToolAdapterTest.kt

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,12 @@ class ToolAdapterTest {
9898

9999
@Test
100100
fun `ToolAdapterRegistry supportedTools lists all tools`() {
101-
assertEquals(setOf("claude", "github-copilot", "opencode"), ToolAdapterRegistry.supportedTools())
101+
assertEquals(setOf("claude", "github-copilot", "codex", "opencode"), ToolAdapterRegistry.supportedTools())
102102
}
103103

104104
@Test
105-
fun `ToolAdapterRegistry all returns three adapters`() {
106-
assertEquals(3, ToolAdapterRegistry.all().size)
105+
fun `ToolAdapterRegistry all returns four adapters`() {
106+
assertEquals(4, ToolAdapterRegistry.all().size)
107107
}
108108

109109
// ── Agent parsing ────────────────────────────────────
@@ -154,6 +154,37 @@ class ToolAdapterTest {
154154
assertEquals(listOf("github-copilot", "claude"), OpenSpecSettingsPlugin.parseAgents("GitHub,Claude"))
155155
}
156156

157+
// ── Codex adapter ────────────────────────────────────
158+
159+
@Test
160+
fun `CodexAdapter command path uses codex skills dir`() {
161+
// Codex unifies commands as skills
162+
assertEquals(".codex/skills/opsx-propose/SKILL.md", CodexAdapter.getCommandFilePath("propose"))
163+
}
164+
165+
@Test
166+
fun `CodexAdapter skill path uses codex skills dir`() {
167+
assertEquals(".codex/skills/test-skill/SKILL.md", CodexAdapter.getSkillFilePath("test-skill"))
168+
}
169+
170+
@Test
171+
fun `CodexAdapter command format has YAML frontmatter`() {
172+
val output = CodexAdapter.formatCommandFile(sampleCommand)
173+
assertTrue(output.startsWith("---\n"))
174+
assertTrue(output.contains("name: opsx-test-cmd"))
175+
assertTrue(output.contains("description:"))
176+
}
177+
178+
@Test
179+
fun `ToolAdapterRegistry returns CodexAdapter for codex`() {
180+
assertEquals(CodexAdapter, ToolAdapterRegistry.get("codex"))
181+
}
182+
183+
@Test
184+
fun `parseAgents maps codex to codex`() {
185+
assertEquals(listOf("codex"), OpenSpecSettingsPlugin.parseAgents("codex"))
186+
}
187+
157188
// ── OpenCode adapter ─────────────────────────────────
158189

159190
@Test

0 commit comments

Comments
 (0)