diff --git a/src/features/rules/copilot-rule.test.ts b/src/features/rules/copilot-rule.test.ts index bf874da96..909ae1450 100644 --- a/src/features/rules/copilot-rule.test.ts +++ b/src/features/rules/copilot-rule.test.ts @@ -594,6 +594,55 @@ This is test rule content from file.`; expect(copilotRule.isRoot()).toBe(true); }); + it("should use relativeDirPath to distinguish non-root files named copilot-instructions.md", async () => { + const instructionsDir = join(testDir, ".github", "instructions"); + await ensureDir(instructionsDir); + + const fileContent = `--- +description: "Same filename as root" +applyTo: "**/*.ts" +--- + +This should be treated as a non-root rule.`; + await writeFileContent(join(instructionsDir, "copilot-instructions.md"), fileContent); + + const copilotRule = await CopilotRule.fromFile({ + baseDir: testDir, + relativeDirPath: ".github/instructions", + relativeFilePath: "copilot-instructions.md", + validate: true, + }); + + expect(copilotRule.isRoot()).toBe(false); + expect(copilotRule.getRelativeDirPath()).toBe(".github/instructions"); + expect(copilotRule.getRelativeFilePath()).toBe("copilot-instructions.instructions.md"); + expect(copilotRule.getFrontmatter()).toEqual({ + description: "Same filename as root", + applyTo: "**/*.ts", + }); + expect(copilotRule.getBody()).toBe("This should be treated as a non-root rule."); + }); + + it("should detect root only when both relativeDirPath and filename match", async () => { + const githubDir = join(testDir, ".github"); + await ensureDir(githubDir); + + const rootContent = "Root detected with explicit directory."; + await writeFileContent(join(githubDir, "copilot-instructions.md"), rootContent); + + const copilotRule = await CopilotRule.fromFile({ + baseDir: testDir, + relativeDirPath: ".github", + relativeFilePath: "copilot-instructions.md", + validate: true, + }); + + expect(copilotRule.isRoot()).toBe(true); + expect(copilotRule.getRelativeDirPath()).toBe(".github"); + expect(copilotRule.getRelativeFilePath()).toBe("copilot-instructions.md"); + expect(copilotRule.getBody()).toBe(rootContent); + }); + it("should load root file from .copilot/copilot-instructions.md when global=true", async () => { const copilotDir = join(testDir, ".copilot"); await ensureDir(copilotDir); diff --git a/src/features/rules/copilot-rule.ts b/src/features/rules/copilot-rule.ts index ee3fe625a..7599dedab 100644 --- a/src/features/rules/copilot-rule.ts +++ b/src/features/rules/copilot-rule.ts @@ -202,13 +202,21 @@ export class CopilotRule extends ToolRule { static async fromFile({ baseDir = process.cwd(), + relativeDirPath, relativeFilePath, validate = true, global = false, }: ToolRuleFromFileParams): Promise { const paths = this.getSettablePaths({ global }); - // Determine if this is a root file based on the file path - const isRoot = relativeFilePath === paths.root.relativeFilePath; + const isRoot = relativeDirPath + ? join(relativeDirPath, relativeFilePath) === + join(paths.root.relativeDirPath, paths.root.relativeFilePath) + : relativeFilePath === paths.root.relativeFilePath; + const resolvedRelativeDirPath = + relativeDirPath ?? + (isRoot + ? paths.root.relativeDirPath + : (paths.nonRoot?.relativeDirPath ?? paths.root.relativeDirPath)); if (isRoot) { const relativePath = join(paths.root.relativeDirPath, paths.root.relativeFilePath); @@ -230,7 +238,7 @@ export class CopilotRule extends ToolRule { throw new Error(`nonRoot path is not set for ${relativeFilePath}`); } - const relativePath = join(paths.nonRoot.relativeDirPath, relativeFilePath); + const relativePath = join(resolvedRelativeDirPath, relativeFilePath); const filePath = join(baseDir, relativePath); const fileContent = await readFileContent(filePath); @@ -244,7 +252,7 @@ export class CopilotRule extends ToolRule { return new CopilotRule({ baseDir: baseDir, - relativeDirPath: paths.nonRoot.relativeDirPath, + relativeDirPath: resolvedRelativeDirPath, relativeFilePath: relativeFilePath.endsWith(".instructions.md") ? relativeFilePath : relativeFilePath.replace(/\.md$/, ".instructions.md"), @@ -262,7 +270,9 @@ export class CopilotRule extends ToolRule { global = false, }: ToolRuleForDeletionParams): CopilotRule { const paths = this.getSettablePaths({ global }); - const isRoot = relativeFilePath === paths.root.relativeFilePath; + const isRoot = + join(relativeDirPath, relativeFilePath) === + join(paths.root.relativeDirPath, paths.root.relativeFilePath); return new CopilotRule({ baseDir,