Skip to content

Commit 0f53452

Browse files
authored
Merge pull request #1355 from Hiroshiba/feat/codexcli-global-subagents
feat: enable global Codex CLI subagent support
2 parents 62ccbea + 9121dcd commit 0f53452

7 files changed

Lines changed: 83 additions & 6 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ See [Quick Start guide](https://dyoshikawa.github.io/rulesync/getting-started/qu
6767
| AGENTS.md | agentsmd || | | 🎮 | 🎮 | 🎮 | |
6868
| AgentsSkills | agentsskills | | | | | || |
6969
| Claude Code | claudecode | ✅ 🌏 || ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
70-
| Codex CLI | codexcli | ✅ 🌏 | | ✅ 🌏 🔧 | 🌏 | | ✅ 🌏 | |
70+
| Codex CLI | codexcli | ✅ 🌏 | | ✅ 🌏 🔧 | 🌏 | ✅ 🌏 | ✅ 🌏 | |
7171
| Gemini CLI | geminicli | ✅ 🌏 || ✅ 🌏 | ✅ 🌏 | 🎮 | ✅ 🌏 | ✅ 🌏 |
7272
| Goose | goose | ✅ 🌏 || | | | | |
7373
| GitHub Copilot | copilot | ✅ 🌏 | ||||||

docs/reference/supported-tools.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Rulesync supports both **generation** and **import** for All of the major AI cod
77
| AGENTS.md | agentsmd || | | 🎮 | 🎮 | 🎮 | |
88
| AgentsSkills | agentsskills | | | | | || |
99
| Claude Code | claudecode | ✅ 🌏 || ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
10-
| Codex CLI | codexcli | ✅ 🌏 | | ✅ 🌏 🔧 | 🌏 | | ✅ 🌏 | |
10+
| Codex CLI | codexcli | ✅ 🌏 | | ✅ 🌏 🔧 | 🌏 | ✅ 🌏 | ✅ 🌏 | |
1111
| Gemini CLI | geminicli | ✅ 🌏 || ✅ 🌏 | ✅ 🌏 | 🎮 | ✅ 🌏 | ✅ 🌏 |
1212
| GitHub Copilot | copilot | ✅ 🌏 | ||||||
1313
| Goose | goose | ✅ 🌏 || | | | | |

skills/rulesync/supported-tools.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Rulesync supports both **generation** and **import** for All of the major AI cod
77
| AGENTS.md | agentsmd || | | 🎮 | 🎮 | 🎮 | |
88
| AgentsSkills | agentsskills | | | | | || |
99
| Claude Code | claudecode | ✅ 🌏 || ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
10-
| Codex CLI | codexcli | ✅ 🌏 | | ✅ 🌏 🔧 | 🌏 | | ✅ 🌏 | |
10+
| Codex CLI | codexcli | ✅ 🌏 | | ✅ 🌏 🔧 | 🌏 | ✅ 🌏 | ✅ 🌏 | |
1111
| Gemini CLI | geminicli | ✅ 🌏 || ✅ 🌏 | ✅ 🌏 | 🎮 | ✅ 🌏 | ✅ 🌏 |
1212
| GitHub Copilot | copilot | ✅ 🌏 | ||||||
1313
| Goose | goose | ✅ 🌏 || | | | | |

src/e2e/e2e-subagents.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ describe("E2E: subagents (global mode)", () => {
109109

110110
it.each([
111111
{ target: "claudecode", outputPath: join(".claude", "agents", "planner.md") },
112+
{ target: "codexcli", outputPath: join(".codex", "agents", "planner.toml") },
112113
{ target: "cursor", outputPath: join(".cursor", "agents", "planner.md") },
113114
{ target: "opencode", outputPath: join(".config", "opencode", "agent", "planner.md") },
114115
])("should generate $target subagents in home directory", async ({ target, outputPath }) => {

src/features/subagents/codexcli-subagent.test.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,60 @@ describe("CodexCliSubagent", () => {
325325
expect(codexcliSubagent.getBody()).not.toContain("description");
326326
expect(codexcliSubagent.getBody()).not.toContain("developer_instructions");
327327
});
328+
329+
it("should use same relative path when global is true", () => {
330+
const rulesyncSubagent = new RulesyncSubagent({
331+
baseDir: testDir,
332+
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
333+
relativeFilePath: "global-agent.md",
334+
frontmatter: {
335+
targets: ["codexcli"],
336+
name: "global-agent",
337+
description: "A global agent",
338+
},
339+
body: "Global agent content",
340+
validate: true,
341+
});
342+
343+
const codexcliSubagent = CodexCliSubagent.fromRulesyncSubagent({
344+
baseDir: testDir,
345+
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
346+
rulesyncSubagent,
347+
validate: true,
348+
global: true,
349+
}) as CodexCliSubagent;
350+
351+
expect(codexcliSubagent).toBeInstanceOf(CodexCliSubagent);
352+
expect(codexcliSubagent.getRelativeDirPath()).toBe(join(".codex", "agents"));
353+
expect(codexcliSubagent.getBody()).toContain('name = "global-agent"');
354+
});
355+
356+
it("should use same relative path when global is false", () => {
357+
const rulesyncSubagent = new RulesyncSubagent({
358+
baseDir: testDir,
359+
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
360+
relativeFilePath: "local-agent.md",
361+
frontmatter: {
362+
targets: ["codexcli"],
363+
name: "local-agent",
364+
description: "A local agent",
365+
},
366+
body: "Local agent content",
367+
validate: true,
368+
});
369+
370+
const codexcliSubagent = CodexCliSubagent.fromRulesyncSubagent({
371+
baseDir: testDir,
372+
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
373+
rulesyncSubagent,
374+
validate: true,
375+
global: false,
376+
}) as CodexCliSubagent;
377+
378+
expect(codexcliSubagent).toBeInstanceOf(CodexCliSubagent);
379+
expect(codexcliSubagent.getRelativeDirPath()).toBe(join(".codex", "agents"));
380+
expect(codexcliSubagent.getBody()).toContain('name = "local-agent"');
381+
});
328382
});
329383

330384
describe("fromFile", () => {
@@ -372,6 +426,28 @@ describe("CodexCliSubagent", () => {
372426
}),
373427
).rejects.toThrow();
374428
});
429+
430+
it("should load with same relative path when global is true", async () => {
431+
const agentsDir = join(testDir, ".codex", "agents");
432+
const tomlContent = [
433+
'name = "global-test-agent"',
434+
'description = "A global test agent"',
435+
'developer_instructions = "Global agent body"',
436+
].join("\n");
437+
438+
await writeFileContent(join(agentsDir, "global-test-agent.toml"), tomlContent);
439+
440+
const subagent = await CodexCliSubagent.fromFile({
441+
baseDir: testDir,
442+
relativeFilePath: "global-test-agent.toml",
443+
validate: true,
444+
global: true,
445+
});
446+
447+
expect(subagent).toBeInstanceOf(CodexCliSubagent);
448+
expect(subagent.getRelativeDirPath()).toBe(join(".codex", "agents"));
449+
expect(subagent.getBody()).toContain('name = "global-test-agent"');
450+
});
375451
});
376452

377453
describe("validate", () => {

src/features/subagents/subagents-processor.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -918,13 +918,14 @@ Second global content`;
918918
});
919919

920920
describe("getToolTargets with global: true", () => {
921-
it("should return claudecode, cursor, factorydroid, and opencode as global-supported targets", () => {
921+
it("should return claudecode, codexcli, cursor, factorydroid, and opencode as global-supported targets", () => {
922922
const toolTargets = SubagentsProcessor.getToolTargets({ global: true });
923923

924924
expect(Array.isArray(toolTargets)).toBe(true);
925925
expect(toolTargets).toEqual([
926926
"claudecode",
927927
"claudecode-legacy",
928+
"codexcli",
928929
"cursor",
929930
"factorydroid",
930931
"opencode",
@@ -935,7 +936,6 @@ Second global content`;
935936
const toolTargets = SubagentsProcessor.getToolTargets({ global: true });
936937

937938
expect(toolTargets).not.toContain("copilot");
938-
expect(toolTargets).not.toContain("codexcli");
939939
expect(toolTargets).not.toContain("agentsmd");
940940
expect(toolTargets).not.toContain("geminicli");
941941
expect(toolTargets).not.toContain("roo");

src/features/subagents/subagents-processor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ const toolSubagentFactories = new Map<SubagentsProcessorToolTarget, ToolSubagent
106106
"codexcli",
107107
{
108108
class: CodexCliSubagent,
109-
meta: { supportsSimulated: false, supportsGlobal: false, filePattern: "*.toml" },
109+
meta: { supportsSimulated: false, supportsGlobal: true, filePattern: "*.toml" },
110110
},
111111
],
112112
[

0 commit comments

Comments
 (0)