diff --git a/src/features/subagents/copilot-subagent.test.ts b/src/features/subagents/copilot-subagent.test.ts index ae4608f52..cd8503f79 100644 --- a/src/features/subagents/copilot-subagent.test.ts +++ b/src/features/subagents/copilot-subagent.test.ts @@ -96,6 +96,81 @@ Plan tasks`; expect(subagent.getFrontmatter().tools).toEqual(["agent/runSubagent"]); }); + + it("converts .md extension to .agent.md in output file path", () => { + const rulesyncSubagent = new RulesyncSubagent({ + baseDir: testDir, + relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, + relativeFilePath: "planner.md", + frontmatter: { + targets: ["copilot"], + name: "planner", + description: "Plan things", + copilot: {}, + }, + body: "Plan tasks", + validate: true, + }); + + const subagent = CopilotSubagent.fromRulesyncSubagent({ + baseDir: testDir, + relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, + rulesyncSubagent, + validate: true, + }) as CopilotSubagent; + + expect(subagent.getRelativeFilePath()).toBe("planner.agent.md"); + }); + + it("preserves .agent.md extension when already present", () => { + const rulesyncSubagent = new RulesyncSubagent({ + baseDir: testDir, + relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, + relativeFilePath: "planner.agent.md", + frontmatter: { + targets: ["copilot"], + name: "planner", + description: "Plan things", + copilot: {}, + }, + body: "Plan tasks", + validate: true, + }); + + const subagent = CopilotSubagent.fromRulesyncSubagent({ + baseDir: testDir, + relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, + rulesyncSubagent, + validate: true, + }) as CopilotSubagent; + + expect(subagent.getRelativeFilePath()).toBe("planner.agent.md"); + }); + + it("throws when source file path does not end in .md", () => { + const rulesyncSubagent = new RulesyncSubagent({ + baseDir: testDir, + relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, + relativeFilePath: "planner.txt", + frontmatter: { + targets: ["copilot"], + name: "planner", + description: "Plan things", + copilot: {}, + }, + body: "Plan tasks", + validate: true, + }); + + expect(() => + CopilotSubagent.fromRulesyncSubagent({ + baseDir: testDir, + relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, + rulesyncSubagent, + validate: true, + }), + ).toThrow("Expected .md file path"); + }); }); describe("toRulesyncSubagent", () => { @@ -124,6 +199,25 @@ Plan tasks`; }); expect(rulesyncSubagent.getBody()).toBe("Plan tasks"); }); + + it("converts .agent.md back to .md for rulesync file path", () => { + const subagent = new CopilotSubagent({ + baseDir: testDir, + relativeDirPath: ".github/agents", + relativeFilePath: "planner.agent.md", + frontmatter: { + name: "planner", + description: "Plan things", + }, + body: "Plan tasks", + fileContent: validContent, + validate: true, + }); + + const rulesyncSubagent = subagent.toRulesyncSubagent(); + + expect(rulesyncSubagent.getRelativeFilePath()).toBe("planner.md"); + }); }); describe("fromFile", () => { diff --git a/src/features/subagents/copilot-subagent.ts b/src/features/subagents/copilot-subagent.ts index 5b4b62a28..4f9e03c8e 100644 --- a/src/features/subagents/copilot-subagent.ts +++ b/src/features/subagents/copilot-subagent.ts @@ -31,6 +31,25 @@ type CopilotSubagentParams = { body: string; } & AiFileParams; +const AGENT_MD_EXTENSION = ".agent.md"; + +const toAgentMdFilePath = (filePath: string): string => { + if (filePath.endsWith(AGENT_MD_EXTENSION)) { + return filePath; + } + if (filePath.endsWith(".md")) { + return filePath.slice(0, -3) + AGENT_MD_EXTENSION; + } + throw new Error(`Expected .md file path, got: ${filePath}`); +}; + +const fromAgentMdFilePath = (filePath: string): string => { + if (filePath.endsWith(AGENT_MD_EXTENSION)) { + return filePath.slice(0, -AGENT_MD_EXTENSION.length) + ".md"; + } + return filePath; +}; + const normalizeTools = (tools: string | string[] | undefined): string[] => { if (!tools) { return []; @@ -98,7 +117,7 @@ export class CopilotSubagent extends ToolSubagent { frontmatter: rulesyncFrontmatter, body: this.body, relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, - relativeFilePath: this.getRelativeFilePath(), + relativeFilePath: fromAgentMdFilePath(this.getRelativeFilePath()), validate: true, }); } @@ -134,7 +153,7 @@ export class CopilotSubagent extends ToolSubagent { frontmatter: copilotFrontmatter, body, relativeDirPath: paths.relativeDirPath, - relativeFilePath: rulesyncSubagent.getRelativeFilePath(), + relativeFilePath: toAgentMdFilePath(rulesyncSubagent.getRelativeFilePath()), fileContent, validate, global, diff --git a/src/features/subagents/subagents-processor.test.ts b/src/features/subagents/subagents-processor.test.ts index 7e4012eb2..67440afb7 100644 --- a/src/features/subagents/subagents-processor.test.ts +++ b/src/features/subagents/subagents-processor.test.ts @@ -646,7 +646,7 @@ description: Copilot agent description --- Copilot agent content`; - await writeFileContent(join(subagentsDir, "copilot-agent.md"), subagentContent); + await writeFileContent(join(subagentsDir, "copilot-agent.agent.md"), subagentContent); const toolFiles = await processor.loadToolFiles(); diff --git a/src/features/subagents/subagents-processor.ts b/src/features/subagents/subagents-processor.ts index 9675b99d2..62f8a0a9a 100644 --- a/src/features/subagents/subagents-processor.ts +++ b/src/features/subagents/subagents-processor.ts @@ -119,7 +119,7 @@ const toolSubagentFactories = new Map