Skip to content

Commit 70c856a

Browse files
committed
fix: use directory-aware root detection in CopilotRule.fromFile
1 parent ec9ee7c commit 70c856a

2 files changed

Lines changed: 64 additions & 5 deletions

File tree

src/features/rules/copilot-rule.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,55 @@ This is test rule content from file.`;
594594
expect(copilotRule.isRoot()).toBe(true);
595595
});
596596

597+
it("should use relativeDirPath to distinguish non-root files named copilot-instructions.md", async () => {
598+
const instructionsDir = join(testDir, ".github", "instructions");
599+
await ensureDir(instructionsDir);
600+
601+
const fileContent = `---
602+
description: "Same filename as root"
603+
applyTo: "**/*.ts"
604+
---
605+
606+
This should be treated as a non-root rule.`;
607+
await writeFileContent(join(instructionsDir, "copilot-instructions.md"), fileContent);
608+
609+
const copilotRule = await CopilotRule.fromFile({
610+
baseDir: testDir,
611+
relativeDirPath: ".github/instructions",
612+
relativeFilePath: "copilot-instructions.md",
613+
validate: true,
614+
});
615+
616+
expect(copilotRule.isRoot()).toBe(false);
617+
expect(copilotRule.getRelativeDirPath()).toBe(".github/instructions");
618+
expect(copilotRule.getRelativeFilePath()).toBe("copilot-instructions.instructions.md");
619+
expect(copilotRule.getFrontmatter()).toEqual({
620+
description: "Same filename as root",
621+
applyTo: "**/*.ts",
622+
});
623+
expect(copilotRule.getBody()).toBe("This should be treated as a non-root rule.");
624+
});
625+
626+
it("should detect root only when both relativeDirPath and filename match", async () => {
627+
const githubDir = join(testDir, ".github");
628+
await ensureDir(githubDir);
629+
630+
const rootContent = "Root detected with explicit directory.";
631+
await writeFileContent(join(githubDir, "copilot-instructions.md"), rootContent);
632+
633+
const copilotRule = await CopilotRule.fromFile({
634+
baseDir: testDir,
635+
relativeDirPath: ".github",
636+
relativeFilePath: "copilot-instructions.md",
637+
validate: true,
638+
});
639+
640+
expect(copilotRule.isRoot()).toBe(true);
641+
expect(copilotRule.getRelativeDirPath()).toBe(".github");
642+
expect(copilotRule.getRelativeFilePath()).toBe("copilot-instructions.md");
643+
expect(copilotRule.getBody()).toBe(rootContent);
644+
});
645+
597646
it("should load root file from .copilot/copilot-instructions.md when global=true", async () => {
598647
const copilotDir = join(testDir, ".copilot");
599648
await ensureDir(copilotDir);

src/features/rules/copilot-rule.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,21 @@ export class CopilotRule extends ToolRule {
202202

203203
static async fromFile({
204204
baseDir = process.cwd(),
205+
relativeDirPath,
205206
relativeFilePath,
206207
validate = true,
207208
global = false,
208209
}: ToolRuleFromFileParams): Promise<CopilotRule> {
209210
const paths = this.getSettablePaths({ global });
210-
// Determine if this is a root file based on the file path
211-
const isRoot = relativeFilePath === paths.root.relativeFilePath;
211+
const isRoot = relativeDirPath
212+
? join(relativeDirPath, relativeFilePath) ===
213+
join(paths.root.relativeDirPath, paths.root.relativeFilePath)
214+
: relativeFilePath === paths.root.relativeFilePath;
215+
const resolvedRelativeDirPath =
216+
relativeDirPath ??
217+
(isRoot
218+
? paths.root.relativeDirPath
219+
: (paths.nonRoot?.relativeDirPath ?? paths.root.relativeDirPath));
212220

213221
if (isRoot) {
214222
const relativePath = join(paths.root.relativeDirPath, paths.root.relativeFilePath);
@@ -230,7 +238,7 @@ export class CopilotRule extends ToolRule {
230238
throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
231239
}
232240

233-
const relativePath = join(paths.nonRoot.relativeDirPath, relativeFilePath);
241+
const relativePath = join(resolvedRelativeDirPath, relativeFilePath);
234242
const filePath = join(baseDir, relativePath);
235243
const fileContent = await readFileContent(filePath);
236244

@@ -244,7 +252,7 @@ export class CopilotRule extends ToolRule {
244252

245253
return new CopilotRule({
246254
baseDir: baseDir,
247-
relativeDirPath: paths.nonRoot.relativeDirPath,
255+
relativeDirPath: resolvedRelativeDirPath,
248256
relativeFilePath: relativeFilePath.endsWith(".instructions.md")
249257
? relativeFilePath
250258
: relativeFilePath.replace(/\.md$/, ".instructions.md"),
@@ -262,7 +270,9 @@ export class CopilotRule extends ToolRule {
262270
global = false,
263271
}: ToolRuleForDeletionParams): CopilotRule {
264272
const paths = this.getSettablePaths({ global });
265-
const isRoot = relativeFilePath === paths.root.relativeFilePath;
273+
const isRoot =
274+
join(relativeDirPath, relativeFilePath) ===
275+
join(paths.root.relativeDirPath, paths.root.relativeFilePath);
266276

267277
return new CopilotRule({
268278
baseDir,

0 commit comments

Comments
 (0)