Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/features/rules/copilot-rule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
20 changes: 15 additions & 5 deletions src/features/rules/copilot-rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,21 @@ export class CopilotRule extends ToolRule {

static async fromFile({
baseDir = process.cwd(),
relativeDirPath,
relativeFilePath,
validate = true,
global = false,
}: ToolRuleFromFileParams): Promise<CopilotRule> {
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);
Expand All @@ -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);

Expand All @@ -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"),
Expand All @@ -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,
Expand Down
Loading