Skip to content
This repository was archived by the owner on May 20, 2026. It is now read-only.
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
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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<vscode.ChatSessionCustomizationItem[]> {
// 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);
Comment thread
joshspicer marked this conversation as resolved.
} catch {
// file doesn't exist
}
}
}

const items: vscode.ChatSessionCustomizationItem[] = [];
const seenUris = new Set<string>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ describe('CopilotCLICustomizationProvider', () => {
mockCustomInstructionsService,
mockPromptsService,
new TestLogService(),
{ getWorkspaceFolders: () => [] } as any,
{ stat: () => Promise.reject(new Error('not found')) } as any,
));
});

Expand Down Expand Up @@ -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 }]);
Expand Down
Loading