From 74f5df595d45b022797c84f121d11315756de4a6 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Sun, 5 Apr 2026 19:17:43 -0700 Subject: [PATCH] fix: discover AGENTS.md and CLAUDE.md at workspace roots The provider only found copilot-instructions.md (via customInstructionsService.getAgentInstructions()). AGENTS.md and CLAUDE.md live at the workspace root and are also agent instructions but weren't discovered. Now stats workspace roots for these files and includes them as agent-instructions items. --- .../copilotCLICustomizationProvider.ts | 21 +++++++++++- .../copilotCLICustomizationProvider.spec.ts | 32 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/extension/chatSessions/vscode-node/copilotCLICustomizationProvider.ts b/src/extension/chatSessions/vscode-node/copilotCLICustomizationProvider.ts index 17bbe462cc..e94e7f0b6d 100644 --- a/src/extension/chatSessions/vscode-node/copilotCLICustomizationProvider.ts +++ b/src/extension/chatSessions/vscode-node/copilotCLICustomizationProvider.ts @@ -7,13 +7,16 @@ import * as l10n from '@vscode/l10n'; import * as vscode from 'vscode'; import { ICustomInstructionsService } from '../../../platform/customInstructions/common/customInstructionsService'; import { INSTRUCTION_FILE_EXTENSION, SKILL_FILENAME } from '../../../platform/customInstructions/common/promptTypes'; +import { IFileSystemService } from '../../../platform/filesystem/common/fileSystemService'; import { ILogService } from '../../../platform/log/common/logService'; import { IPromptsService } from '../../../platform/promptFiles/common/promptsService'; +import { IWorkspaceService } from '../../../platform/workspace/common/workspaceService'; import { CancellationToken } from '../../../util/vs/base/common/cancellation'; import { isCancellationError } from '../../../util/vs/base/common/errors'; import { Emitter } from '../../../util/vs/base/common/event'; import { Disposable } from '../../../util/vs/base/common/lifecycle'; import { basename } from '../../../util/vs/base/common/resources'; +import { URI } from '../../../util/vs/base/common/uri'; import { IChatPromptFileService } from '../common/chatPromptFileService'; import { ICopilotCLIAgents } from '../copilotcli/node/copilotCli'; @@ -42,6 +45,8 @@ export class CopilotCLICustomizationProvider extends Disposable implements vscod @ICustomInstructionsService private readonly customInstructionsService: ICustomInstructionsService, @IPromptsService private readonly promptsService: IPromptsService, @ILogService private readonly logService: ILogService, + @IWorkspaceService private readonly workspaceService: IWorkspaceService, + @IFileSystemService private readonly fileSystemService: IFileSystemService, ) { super(); @@ -90,12 +95,26 @@ export class CopilotCLICustomizationProvider extends Disposable implements vscod * Collects all instruction items from the prompt file service, * categorizing them with groupKeys and badges matching the core * implementation: - * - agent-instructions: copilot-instructions.md files + * - agent-instructions: AGENTS.md, CLAUDE.md, copilot-instructions.md * - context-instructions: files with an applyTo pattern (badge = pattern) * - on-demand-instructions: files without an applyTo pattern */ private async getInstructionItems(token: CancellationToken): Promise { + // Collect agent instruction URIs from customInstructionsService + // (copilot-instructions.md) plus workspace-root AGENTS.md and CLAUDE.md const agentInstructionUriList = await this.customInstructionsService.getAgentInstructions(); + const rootFileNames = ['AGENTS.md', 'CLAUDE.md']; + for (const folder of this.workspaceService.getWorkspaceFolders()) { + for (const fileName of rootFileNames) { + const uri = URI.joinPath(folder, fileName); + try { + await this.fileSystemService.stat(uri); + agentInstructionUriList.push(uri); + } catch { + // file doesn't exist + } + } + } const items: vscode.ChatSessionCustomizationItem[] = []; const seenUris = new Set(); diff --git a/src/extension/chatSessions/vscode-node/test/copilotCLICustomizationProvider.spec.ts b/src/extension/chatSessions/vscode-node/test/copilotCLICustomizationProvider.spec.ts index 2688044d11..1d26156100 100644 --- a/src/extension/chatSessions/vscode-node/test/copilotCLICustomizationProvider.spec.ts +++ b/src/extension/chatSessions/vscode-node/test/copilotCLICustomizationProvider.spec.ts @@ -160,6 +160,8 @@ describe('CopilotCLICustomizationProvider', () => { mockCustomInstructionsService, mockPromptsService, new TestLogService(), + { getWorkspaceFolders: () => [] } as any, + { stat: () => Promise.reject(new Error('not found')) } as any, )); }); @@ -364,6 +366,36 @@ describe('CopilotCLICustomizationProvider', () => { expect(instrItems.map(i => i.name)).toEqual(['AGENTS.md', 'CLAUDE.md', 'copilot-instructions.md']); }); + it('discovers AGENTS.md and CLAUDE.md from workspace roots via filesystem', async () => { + const workspaceRoot = URI.file('/workspace'); + const agentsUri = URI.file('/workspace/AGENTS.md'); + const claudeUri = URI.file('/workspace/CLAUDE.md'); + const existingUris = new Set([agentsUri.toString(), claudeUri.toString()]); + + const testProvider = disposables.add(new CopilotCLICustomizationProvider( + mockPromptFileService, + mockCopilotCLIAgents, + mockCustomInstructionsService, + mockPromptsService, + new TestLogService(), + { getWorkspaceFolders: () => [workspaceRoot] } as any, + { + stat: (uri: URI) => existingUris.has(uri.toString()) + ? Promise.resolve({ type: 1, ctime: 0, mtime: 0, size: 0 }) + : Promise.reject(new Error('not found')), + } as any, + )); + + mockPromptFileService.setInstructions([]); + mockCustomInstructionsService.setAgentInstructionUris([]); + + const items = await testProvider.provideChatSessionCustomizations(undefined!); + const instrItems = items.filter(i => i.type === FakeChatSessionCustomizationType.Instructions); + expect(instrItems).toHaveLength(2); + expect(instrItems.every(i => i.groupKey === 'agent-instructions')).toBe(true); + expect(instrItems.map(i => i.name)).toEqual(['AGENTS.md', 'CLAUDE.md']); + }); + it('uses context-instructions groupKey with badge for instructions with applyTo pattern', async () => { const uri = URI.file('/workspace/.github/style.instructions.md'); mockPromptFileService.setInstructions([{ uri }]);