fix(coding-agent): honor comma-separated and **/* Copilot applyTo globs

GitHub documents applyTo as a single comma-separated string (e.g.
"**/*.ts,**/*.tsx") and treats both ** and **/* as all-files. The rule
loader kept the whole CSV value as one glob and missed **/* for
always-apply. Split applyTo via parseCSV and include **/* as all-files.

Addresses review feedback on #2734.
This commit is contained in:
can1357
2026-06-16 14:23:50 +02:00
parent 063ce1ba00
commit c509a5d59c
2 changed files with 24 additions and 12 deletions
+6 -12
View File
@@ -216,22 +216,16 @@ function transformInstructionRule(
}
function normalizeApplyToGlobs(value: unknown): string[] | undefined {
if (typeof value === "string") {
const glob = value.trim();
return glob ? [glob] : undefined;
}
if (!Array.isArray(value)) {
return undefined;
}
const globs = value
.filter((item): item is string => typeof item === "string")
.map(item => item.trim())
.filter(Boolean);
// GitHub documents applyTo as a single comma-separated string (e.g.
// "**/*.ts,**/*.tsx"); also tolerate a YAML array of such strings.
const raw = Array.isArray(value) ? value : [value];
const globs = raw.flatMap(item => (typeof item === "string" ? parseCSV(item) : []));
return globs.length > 0 ? globs : undefined;
}
function isAlwaysApplyGlob(glob: string): boolean {
return glob === "*" || glob === "**";
// GitHub treats "*", "**", and "**/*" as matching every file.
return glob === "*" || glob === "**" || glob === "**/*";
}
function describeInstructionRule(globs: string[] | undefined): string {
@@ -165,6 +165,24 @@ describe("github discovery — Copilot user-global surface", () => {
expect(resource.content.trim()).toBe("C# body");
});
test("splits comma-separated applyTo globs and treats **/* as always-apply (#2731)", async () => {
write(
path.join(cwd, ".github", "instructions", "ts.instructions.md"),
"---\napplyTo: '**/*.ts,**/*.tsx'\n---\nTS body\n",
);
write(path.join(cwd, ".github", "instructions", "all.instructions.md"), "---\napplyTo: '**/*'\n---\nAll body\n");
const result = await loadCapability<Rule>("rules", { cwd, providers: ["github"] });
const ts = result.items.find(rule => rule.name === "ts");
expect(ts?.alwaysApply).toBe(false);
expect(ts?.globs).toEqual(["**/*.ts", "**/*.tsx"]);
const all = result.items.find(rule => rule.name === "all");
expect(all?.alwaysApply).toBe(true);
expect(all?.globs).toBeUndefined();
});
test("disabled github provider suppresses copilot instructions and instruction-file rules (#2731)", async () => {
write(path.join(cwd, ".github", "copilot-instructions.md"), "project guidance");
write(