diff --git a/packages/coding-agent/src/discovery/github.ts b/packages/coding-agent/src/discovery/github.ts index eed93d486..a19db3930 100644 --- a/packages/coding-agent/src/discovery/github.ts +++ b/packages/coding-agent/src/discovery/github.ts @@ -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 { diff --git a/packages/coding-agent/test/discovery/github-copilot.test.ts b/packages/coding-agent/test/discovery/github-copilot.test.ts index 846c282d5..e8b99618b 100644 --- a/packages/coding-agent/test/discovery/github-copilot.test.ts +++ b/packages/coding-agent/test/discovery/github-copilot.test.ts @@ -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("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(